Correcao Sidebar Classico e Rail, Correcao Layout, Ajuste de Breakpoint para Tailwind, Ajuste AppTopbar, Ajuste Menu PopOver, Recriado Paleta de Cores, Inserido algumas animações leves, Reajuste Cor items NOVOS da tabela, Drawer Ajuda Corrigido no Logout, Whatsapp, sms, email, recursos extras
This commit is contained in:
566
docs/USER_ARCHETYPES.html
Normal file
566
docs/USER_ARCHETYPES.html
Normal file
@@ -0,0 +1,566 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>AgenciaPsi — Arquétipos de Usuário</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', system-ui, sans-serif;
|
||||
background: #0f1117;
|
||||
color: #e2e8f0;
|
||||
min-height: 100vh;
|
||||
padding: 2rem 1rem 4rem;
|
||||
}
|
||||
|
||||
/* ── Header ── */
|
||||
.page-header {
|
||||
text-align: center;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
.page-header h1 {
|
||||
font-size: 2rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.04em;
|
||||
background: linear-gradient(135deg, #818cf8, #34d399);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
.page-header p {
|
||||
margin-top: .5rem;
|
||||
color: #64748b;
|
||||
font-size: .95rem;
|
||||
}
|
||||
|
||||
/* ── Grid ── */
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
|
||||
gap: 1.5rem;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* ── Card ── */
|
||||
.card {
|
||||
background: #1e2330;
|
||||
border: 1px solid #2d3548;
|
||||
border-radius: 1.25rem;
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
transition: border-color .2s, transform .2s;
|
||||
}
|
||||
.card:hover {
|
||||
border-color: #4f6ef7;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* ── Card header ── */
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .75rem;
|
||||
}
|
||||
.card-icon {
|
||||
width: 2.75rem;
|
||||
height: 2.75rem;
|
||||
border-radius: .75rem;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-size: 1.25rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.card-title { font-size: 1.05rem; font-weight: 700; line-height: 1.2; }
|
||||
.card-subtitle { font-size: .75rem; color: #64748b; margin-top: 2px; font-family: monospace; }
|
||||
|
||||
/* ── Tree ── */
|
||||
.tree {
|
||||
background: #0f1117;
|
||||
border-radius: .75rem;
|
||||
padding: 1rem 1.1rem;
|
||||
font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
|
||||
font-size: .78rem;
|
||||
line-height: 1.8;
|
||||
}
|
||||
.tree-root { color: #a5b4fc; font-weight: 600; }
|
||||
.tree-branch { color: #475569; }
|
||||
.tree-leaf { color: #cbd5e1; }
|
||||
.tree-comment { color: #475569; font-style: italic; }
|
||||
.tree-key { color: #f472b6; }
|
||||
.tree-val { color: #34d399; }
|
||||
.tree-warn { color: #fb923c; }
|
||||
.tree-new { color: #facc15; }
|
||||
|
||||
/* ── Badges ── */
|
||||
.badges { display: flex; flex-wrap: wrap; gap: .4rem; }
|
||||
.badge {
|
||||
font-size: .68rem;
|
||||
font-weight: 600;
|
||||
padding: .2rem .6rem;
|
||||
border-radius: 9999px;
|
||||
border: 1px solid transparent;
|
||||
letter-spacing: .02em;
|
||||
}
|
||||
.badge-purple { background: #312e81; border-color: #4f46e5; color: #a5b4fc; }
|
||||
.badge-green { background: #064e3b; border-color: #059669; color: #6ee7b7; }
|
||||
.badge-blue { background: #1e3a5f; border-color: #2563eb; color: #93c5fd; }
|
||||
.badge-orange { background: #431407; border-color: #ea580c; color: #fdba74; }
|
||||
.badge-yellow { background: #422006; border-color: #ca8a04; color: #fde047; }
|
||||
.badge-pink { background: #500724; border-color: #db2777; color: #f9a8d4; }
|
||||
.badge-gray { background: #1e293b; border-color: #475569; color: #94a3b8; }
|
||||
.badge-red { background: #450a0a; border-color: #dc2626; color: #fca5a5; }
|
||||
|
||||
/* ── Notes ── */
|
||||
.note {
|
||||
font-size: .75rem;
|
||||
color: #64748b;
|
||||
line-height: 1.5;
|
||||
border-left: 2px solid #2d3548;
|
||||
padding-left: .75rem;
|
||||
}
|
||||
.note strong { color: #94a3b8; }
|
||||
|
||||
/* ── Phase tag ── */
|
||||
.phase {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: .3rem;
|
||||
font-size: .7rem;
|
||||
font-weight: 700;
|
||||
padding: .15rem .55rem;
|
||||
border-radius: 9999px;
|
||||
margin-left: auto;
|
||||
}
|
||||
.phase-1 { background: #064e3b; color: #6ee7b7; border: 1px solid #059669; }
|
||||
.phase-2 { background: #422006; color: #fde047; border: 1px solid #ca8a04; }
|
||||
|
||||
/* ── Section label ── */
|
||||
.section-label {
|
||||
grid-column: 1 / -1;
|
||||
font-size: .7rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: .12em;
|
||||
text-transform: uppercase;
|
||||
color: #475569;
|
||||
padding: .25rem 0;
|
||||
border-bottom: 1px solid #2d3548;
|
||||
margin-bottom: -.25rem;
|
||||
}
|
||||
|
||||
/* ── Legend ── */
|
||||
.legend {
|
||||
max-width: 1400px;
|
||||
margin: 2.5rem auto 0;
|
||||
background: #1e2330;
|
||||
border: 1px solid #2d3548;
|
||||
border-radius: 1.25rem;
|
||||
padding: 1.25rem 1.5rem;
|
||||
}
|
||||
.legend h3 { font-size: .8rem; font-weight: 700; color: #64748b; letter-spacing: .08em; text-transform: uppercase; margin-bottom: .75rem; }
|
||||
.legend-grid { display: flex; flex-wrap: wrap; gap: 1rem 2rem; }
|
||||
.legend-item { display: flex; align-items: center; gap: .5rem; font-size: .78rem; color: #94a3b8; }
|
||||
.legend-dot { width: .65rem; height: .65rem; border-radius: 50%; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="page-header">
|
||||
<h1>AgenciaPsi — Arquétipos de Usuário</h1>
|
||||
<p>Como cada tipo de usuário está estruturado no banco de dados e no sistema de permissões.</p>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
|
||||
<!-- ════════════════════════════════════════ PLATAFORMA ══ -->
|
||||
<div class="section-label">🏛️ Plataforma</div>
|
||||
|
||||
<!-- SaaS Admin -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-icon" style="background:#312e81">🛡️</div>
|
||||
<div>
|
||||
<div class="card-title">SaaS Admin</div>
|
||||
<div class="card-subtitle">profiles.role = 'saas_admin'</div>
|
||||
</div>
|
||||
<span class="phase phase-1">Fase 1</span>
|
||||
</div>
|
||||
|
||||
<div class="tree">
|
||||
<div class="tree-root">Usuário (saas@agenciapsi.com.br)</div>
|
||||
<div> <span class="tree-branch">└──</span> <span class="tree-key">profiles.role</span> <span class="tree-val">'saas_admin'</span></div>
|
||||
<div> <span class="tree-comment">// sem memberships de tenant</span></div>
|
||||
<div> <span class="tree-comment">// acessa /saas/*</span></div>
|
||||
</div>
|
||||
|
||||
<div class="badges">
|
||||
<span class="badge badge-purple">role: saas_admin</span>
|
||||
<span class="badge badge-gray">sem tenant</span>
|
||||
</div>
|
||||
|
||||
<p class="note">Acesso total à plataforma. Gerencia planos, features, assinaturas e usuários. Nunca entra em contexto de tenant.</p>
|
||||
</div>
|
||||
|
||||
<!-- Editor -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-icon" style="background:#1e3a5f">✍️</div>
|
||||
<div>
|
||||
<div class="card-title">Editor de Conteúdo</div>
|
||||
<div class="card-subtitle">profiles.platform_roles[] = 'editor'</div>
|
||||
</div>
|
||||
<span class="phase phase-1">Fase 1</span>
|
||||
</div>
|
||||
|
||||
<div class="tree">
|
||||
<div class="tree-root">Usuário (editor@agenciapsi.com.br)</div>
|
||||
<div> <span class="tree-branch">└──</span> <span class="tree-key">profiles.platform_roles</span> <span class="tree-val">['editor']</span></div>
|
||||
<div> <span class="tree-comment">// sem memberships de tenant</span></div>
|
||||
<div> <span class="tree-comment">// acessa /editor/*</span></div>
|
||||
</div>
|
||||
|
||||
<div class="badges">
|
||||
<span class="badge badge-blue">platform_roles: editor</span>
|
||||
<span class="badge badge-gray">sem tenant</span>
|
||||
</div>
|
||||
|
||||
<p class="note">Papel de plataforma (não de tenant). Gerencia conteúdo público, landing pages, textos. Verificado via <code>platform_roles[]</code>.</p>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════════════════════════════ CLÍNICA ══ -->
|
||||
<div class="section-label">🏥 Clínica</div>
|
||||
|
||||
<!-- Clinic Admin -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-icon" style="background:#064e3b">🏥</div>
|
||||
<div>
|
||||
<div class="card-title">Admin da Clínica</div>
|
||||
<div class="card-subtitle">tenant.kind = 'clinic'</div>
|
||||
</div>
|
||||
<span class="phase phase-1">Fase 1</span>
|
||||
</div>
|
||||
|
||||
<div class="tree">
|
||||
<div class="tree-root">Usuário (admin@clinicax.com.br)</div>
|
||||
<div> <span class="tree-branch">├──</span> <span class="tree-key">profiles.role</span> <span class="tree-val">'tenant_member'</span></div>
|
||||
<div> <span class="tree-branch">└──</span> Membership: <span class="tree-val">Clínica X</span></div>
|
||||
<div> <span class="tree-branch">├──</span> <span class="tree-key">tenant.kind</span> <span class="tree-val">'clinic'</span></div>
|
||||
<div> <span class="tree-branch">├──</span> <span class="tree-key">role</span> <span class="tree-val">'clinic_admin'</span></div>
|
||||
<div> <span class="tree-branch">└──</span> <span class="tree-key">plano</span> <span class="tree-val">clinic_free | clinic_pro</span></div>
|
||||
<div> <span class="tree-comment">// acessa /admin/*</span></div>
|
||||
</div>
|
||||
|
||||
<div class="badges">
|
||||
<span class="badge badge-green">role: clinic_admin</span>
|
||||
<span class="badge badge-blue">tenant: clinic</span>
|
||||
<span class="badge badge-gray">clinic_free</span>
|
||||
<span class="badge badge-purple">clinic_pro</span>
|
||||
</div>
|
||||
|
||||
<p class="note">Dono ou gestor de uma clínica. Gerencia profissionais, pacientes, agenda e módulos da clínica.</p>
|
||||
</div>
|
||||
|
||||
<!-- Terapeuta da Clínica -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-icon" style="background:#1e3a5f">🧑⚕️</div>
|
||||
<div>
|
||||
<div class="card-title">Terapeuta da Clínica</div>
|
||||
<div class="card-subtitle">tenant.kind = 'clinic' / role = 'therapist'</div>
|
||||
</div>
|
||||
<span class="phase phase-1">Fase 1</span>
|
||||
</div>
|
||||
|
||||
<div class="tree">
|
||||
<div class="tree-root">Usuário (terapeuta@clinicax.com.br)</div>
|
||||
<div> <span class="tree-branch">├──</span> <span class="tree-key">profiles.role</span> <span class="tree-val">'tenant_member'</span></div>
|
||||
<div> <span class="tree-branch">└──</span> Membership: <span class="tree-val">Clínica X</span></div>
|
||||
<div> <span class="tree-branch">├──</span> <span class="tree-key">tenant.kind</span> <span class="tree-val">'clinic'</span></div>
|
||||
<div> <span class="tree-branch">├──</span> <span class="tree-key">role</span> <span class="tree-val">'therapist'</span></div>
|
||||
<div> <span class="tree-branch">└──</span> <span class="tree-key">entitlements</span> <span class="tree-val">via plano da clínica</span></div>
|
||||
<div> <span class="tree-comment">// acessa /therapist/*</span></div>
|
||||
</div>
|
||||
|
||||
<div class="badges">
|
||||
<span class="badge badge-green">role: therapist</span>
|
||||
<span class="badge badge-blue">tenant: clinic</span>
|
||||
<span class="badge badge-gray">entitlements da clínica</span>
|
||||
</div>
|
||||
|
||||
<p class="note">Terapeuta vinculado a uma clínica. Seus entitlements vêm do plano do tenant (clínica), não de assinatura pessoal.</p>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════════════════════════════ TERAPEUTA INDEPENDENTE ══ -->
|
||||
<div class="section-label">🧑💼 Terapeuta Independente</div>
|
||||
|
||||
<!-- Terapeuta Solo -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-icon" style="background:#064e3b">🧑💼</div>
|
||||
<div>
|
||||
<div class="card-title">Terapeuta Solo</div>
|
||||
<div class="card-subtitle">tenant.kind = 'saas'</div>
|
||||
</div>
|
||||
<span class="phase phase-1">Fase 1</span>
|
||||
</div>
|
||||
|
||||
<div class="tree">
|
||||
<div class="tree-root">Usuário (terapeuta@gmail.com)</div>
|
||||
<div> <span class="tree-branch">├──</span> <span class="tree-key">profiles.role</span> <span class="tree-val">'tenant_member'</span></div>
|
||||
<div> <span class="tree-branch">└──</span> Membership: <span class="tree-val">Tenant Pessoal</span></div>
|
||||
<div> <span class="tree-branch">├──</span> <span class="tree-key">tenant.kind</span> <span class="tree-val">'saas'</span></div>
|
||||
<div> <span class="tree-branch">├──</span> <span class="tree-key">role</span> <span class="tree-val">'therapist'</span></div>
|
||||
<div> <span class="tree-branch">└──</span> <span class="tree-key">plano</span> <span class="tree-val">therapist_free | therapist_pro</span></div>
|
||||
<div> <span class="tree-comment">// entitlements via v_user_entitlements</span></div>
|
||||
<div> <span class="tree-comment">// acessa /therapist/*</span></div>
|
||||
</div>
|
||||
|
||||
<div class="badges">
|
||||
<span class="badge badge-green">role: therapist</span>
|
||||
<span class="badge badge-gray">tenant: saas (pessoal)</span>
|
||||
<span class="badge badge-gray">therapist_free</span>
|
||||
<span class="badge badge-purple">therapist_pro</span>
|
||||
</div>
|
||||
|
||||
<p class="note">Terapeuta autônomo sem clínica. Assina diretamente. Entitlements vêm de <code>v_user_entitlements</code> (assinatura pessoal).</p>
|
||||
</div>
|
||||
|
||||
<!-- Terapeuta Solo + Clínica -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-icon" style="background:#1e3a5f">🔀</div>
|
||||
<div>
|
||||
<div class="card-title">Terapeuta Solo + Clínica</div>
|
||||
<div class="card-subtitle">2 memberships / contexto switcher</div>
|
||||
</div>
|
||||
<span class="phase phase-1">Fase 1</span>
|
||||
</div>
|
||||
|
||||
<div class="tree">
|
||||
<div class="tree-root">Usuário (terapeuta@gmail.com)</div>
|
||||
<div> <span class="tree-branch">├──</span> Membership A: <span class="tree-val">Tenant Pessoal</span></div>
|
||||
<div> <span class="tree-branch">├──</span> <span class="tree-key">tenant.kind</span> <span class="tree-val">'saas'</span></div>
|
||||
<div> <span class="tree-branch">├──</span> <span class="tree-key">role</span> <span class="tree-val">'therapist'</span></div>
|
||||
<div> <span class="tree-branch">└──</span> <span class="tree-key">plano</span> <span class="tree-val">therapist_pro</span></div>
|
||||
<div> <span class="tree-branch">└──</span> Membership B: <span class="tree-val">Clínica X</span></div>
|
||||
<div> <span class="tree-branch">├──</span> <span class="tree-key">tenant.kind</span> <span class="tree-val">'clinic'</span></div>
|
||||
<div> <span class="tree-branch">└──</span> <span class="tree-key">role</span> <span class="tree-val">'therapist'</span></div>
|
||||
<div> <span class="tree-comment">// switcher de contexto no topbar</span></div>
|
||||
</div>
|
||||
|
||||
<div class="badges">
|
||||
<span class="badge badge-green">role: therapist</span>
|
||||
<span class="badge badge-blue">2 tenants</span>
|
||||
<span class="badge badge-purple">therapist_pro</span>
|
||||
<span class="badge badge-gray">switcher de contexto</span>
|
||||
</div>
|
||||
|
||||
<p class="note">Atua em dois contextos. No tenant pessoal usa PRO. Na clínica usa os entitlements da clínica. Precisa de switcher de tenant no topbar.</p>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════════════════════════════ SUPERVISOR ══ -->
|
||||
<div class="section-label">🎓 Supervisor (Fase 1 — novo)</div>
|
||||
|
||||
<!-- Supervisor Solo -->
|
||||
<div class="card" style="border-color: #ca8a04">
|
||||
<div class="card-header">
|
||||
<div class="card-icon" style="background:#422006">🎓</div>
|
||||
<div>
|
||||
<div class="card-title">Supervisor Solo</div>
|
||||
<div class="card-subtitle">tenant.kind = 'supervisor'</div>
|
||||
</div>
|
||||
<span class="phase phase-1">Fase 1</span>
|
||||
</div>
|
||||
|
||||
<div class="tree">
|
||||
<div class="tree-root">Usuário (supervisor@gmail.com)</div>
|
||||
<div> <span class="tree-branch">├──</span> <span class="tree-key">profiles.role</span> <span class="tree-val">'tenant_member'</span></div>
|
||||
<div> <span class="tree-branch">└──</span> Membership: <span class="tree-new">Tenant Supervisão (novo)</span></div>
|
||||
<div> <span class="tree-branch">├──</span> <span class="tree-key">tenant.kind</span> <span class="tree-new">'supervisor'</span></div>
|
||||
<div> <span class="tree-branch">├──</span> <span class="tree-key">role</span> <span class="tree-new">'supervisor'</span></div>
|
||||
<div> <span class="tree-branch">├──</span> <span class="tree-key">plano</span> <span class="tree-new">supervisor_free | supervisor_pro</span></div>
|
||||
<div> <span class="tree-branch">└──</span> <span class="tree-key">max_supervisees</span> <span class="tree-new">3 | 20</span></div>
|
||||
<div> <span class="tree-comment">// acessa /supervisor/*</span></div>
|
||||
</div>
|
||||
|
||||
<div class="badges">
|
||||
<span class="badge badge-yellow">role: supervisor</span>
|
||||
<span class="badge badge-yellow">tenant: supervisor</span>
|
||||
<span class="badge badge-yellow">supervisor_free</span>
|
||||
<span class="badge badge-orange">supervisor_pro</span>
|
||||
</div>
|
||||
|
||||
<p class="note"><strong>Novo.</strong> Supervisor independente. Tem sua própria sala de supervisão. O plano define o limite de terapeutas supervisionados (<code>plans.max_supervisees</code>).</p>
|
||||
</div>
|
||||
|
||||
<!-- Terapeuta + Supervisor -->
|
||||
<div class="card" style="border-color: #ca8a04">
|
||||
<div class="card-header">
|
||||
<div class="card-icon" style="background:#422006">🔀🎓</div>
|
||||
<div>
|
||||
<div class="card-title">Terapeuta + Supervisor</div>
|
||||
<div class="card-subtitle">2 tenants / 2 papéis</div>
|
||||
</div>
|
||||
<span class="phase phase-1">Fase 1</span>
|
||||
</div>
|
||||
|
||||
<div class="tree">
|
||||
<div class="tree-root">Usuário (terapeuta@gmail.com)</div>
|
||||
<div> <span class="tree-branch">├──</span> Membership A: <span class="tree-val">Tenant Pessoal</span></div>
|
||||
<div> <span class="tree-branch">├──</span> <span class="tree-key">tenant.kind</span> <span class="tree-val">'saas'</span></div>
|
||||
<div> <span class="tree-branch">├──</span> <span class="tree-key">role</span> <span class="tree-val">'therapist'</span></div>
|
||||
<div> <span class="tree-branch">└──</span> <span class="tree-key">plano</span> <span class="tree-val">therapist_pro</span></div>
|
||||
<div> <span class="tree-branch">└──</span> Membership B: <span class="tree-new">Tenant Supervisão (novo)</span></div>
|
||||
<div> <span class="tree-branch">├──</span> <span class="tree-key">tenant.kind</span> <span class="tree-new">'supervisor'</span></div>
|
||||
<div> <span class="tree-branch">├──</span> <span class="tree-key">role</span> <span class="tree-new">'supervisor'</span></div>
|
||||
<div> <span class="tree-branch">└──</span> <span class="tree-key">plano</span> <span class="tree-new">supervisor_pro</span></div>
|
||||
<div> <span class="tree-comment">// switcher: "Meu consultório" / "Minha supervisão"</span></div>
|
||||
</div>
|
||||
|
||||
<div class="badges">
|
||||
<span class="badge badge-green">therapist</span>
|
||||
<span class="badge badge-yellow">supervisor</span>
|
||||
<span class="badge badge-blue">2 tenants</span>
|
||||
<span class="badge badge-purple">therapist_pro</span>
|
||||
<span class="badge badge-orange">supervisor_pro</span>
|
||||
</div>
|
||||
|
||||
<p class="note"><strong>O caso mais comum.</strong> Atua como terapeuta no tenant pessoal e como supervisor no tenant de supervisão. Switcher de contexto no topbar.</p>
|
||||
</div>
|
||||
|
||||
<!-- Terapeuta (clínica) + Supervisor -->
|
||||
<div class="card" style="border-color: #ca8a04">
|
||||
<div class="card-header">
|
||||
<div class="card-icon" style="background:#422006">🏥🎓</div>
|
||||
<div>
|
||||
<div class="card-title">Terapeuta (Clínica) + Supervisor</div>
|
||||
<div class="card-subtitle">3 tenants possíveis</div>
|
||||
</div>
|
||||
<span class="phase phase-1">Fase 1</span>
|
||||
</div>
|
||||
|
||||
<div class="tree">
|
||||
<div class="tree-root">Usuário (terapeuta@clinicax.com.br)</div>
|
||||
<div> <span class="tree-branch">├──</span> Membership A: <span class="tree-val">Clínica X</span></div>
|
||||
<div> <span class="tree-branch">├──</span> <span class="tree-key">tenant.kind</span> <span class="tree-val">'clinic'</span></div>
|
||||
<div> <span class="tree-branch">└──</span> <span class="tree-key">role</span> <span class="tree-val">'therapist'</span></div>
|
||||
<div> <span class="tree-branch">└──</span> Membership B: <span class="tree-new">Tenant Supervisão (novo)</span></div>
|
||||
<div> <span class="tree-branch">├──</span> <span class="tree-key">tenant.kind</span> <span class="tree-new">'supervisor'</span></div>
|
||||
<div> <span class="tree-branch">├──</span> <span class="tree-key">role</span> <span class="tree-new">'supervisor'</span></div>
|
||||
<div> <span class="tree-branch">└──</span> <span class="tree-key">plano</span> <span class="tree-new">supervisor_free | supervisor_pro</span></div>
|
||||
<div> <span class="tree-comment">// supervisão é INDEPENDENTE da clínica</span></div>
|
||||
<div> <span class="tree-comment">// colegas da clínica podem ser supervisionados</span></div>
|
||||
<div> <span class="tree-comment">// via convite no tenant de supervisão</span></div>
|
||||
</div>
|
||||
|
||||
<div class="badges">
|
||||
<span class="badge badge-green">therapist (clínica)</span>
|
||||
<span class="badge badge-yellow">supervisor (independente)</span>
|
||||
<span class="badge badge-orange">supervisor_pro</span>
|
||||
</div>
|
||||
|
||||
<p class="note">Trabalha na clínica como terapeuta <strong>e</strong> supervisiona outros terapeutas (inclusive colegas da clínica) de forma independente. A clínica não interfere na supervisão.</p>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════════════════════════════ FASE 2 ══ -->
|
||||
<div class="section-label">🚀 Fase 2 — Marketplace de Supervisão</div>
|
||||
|
||||
<!-- Clínica com Supervisor Associado -->
|
||||
<div class="card" style="border-color: #475569; opacity: .8">
|
||||
<div class="card-header">
|
||||
<div class="card-icon" style="background:#1e293b">🏥🤝🎓</div>
|
||||
<div>
|
||||
<div class="card-title">Clínica com Supervisor Contratado</div>
|
||||
<div class="card-subtitle">repasse financeiro AgenciaPsi</div>
|
||||
</div>
|
||||
<span class="phase phase-2">Fase 2</span>
|
||||
</div>
|
||||
|
||||
<div class="tree">
|
||||
<div class="tree-root">Clínica X</div>
|
||||
<div> <span class="tree-branch">├──</span> Ativa módulo <span class="tree-key">supervisao</span> (feature)</div>
|
||||
<div> <span class="tree-branch">├──</span> Associa supervisor externo via convite</div>
|
||||
<div> <span class="tree-branch">├──</span> Sessões registradas na plataforma</div>
|
||||
<div> <span class="tree-branch">└──</span> Pagamento via AgenciaPsi</div>
|
||||
<div> </div>
|
||||
<div class="tree-root">Fluxo financeiro</div>
|
||||
<div> <span class="tree-branch">└──</span> Clínica paga <span class="tree-warn">R$ 200/sessão</span></div>
|
||||
<div> <span class="tree-branch">├──</span> Supervisor recebe <span class="tree-val">R$ 180</span></div>
|
||||
<div> <span class="tree-branch">└──</span> AgenciaPsi retém <span class="tree-warn">R$ 20 (10%)</span></div>
|
||||
</div>
|
||||
|
||||
<div class="badges">
|
||||
<span class="badge badge-pink">split de pagamento</span>
|
||||
<span class="badge badge-gray">Stripe Connect / Iugu</span>
|
||||
<span class="badge badge-red">Fase 2</span>
|
||||
</div>
|
||||
|
||||
<p class="note"><strong>Futuro.</strong> A clínica contrata supervisão via plataforma. AgenciaPsi faz o split automático. Requer gateway com marketplace split (Stripe Connect, Iugu, Pagar.me).</p>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════════════════════════════ PACIENTE ══ -->
|
||||
<div class="section-label">👤 Paciente / Portal</div>
|
||||
|
||||
<!-- Paciente -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-icon" style="background:#500724">👤</div>
|
||||
<div>
|
||||
<div class="card-title">Paciente</div>
|
||||
<div class="card-subtitle">profiles.role = 'portal_user'</div>
|
||||
</div>
|
||||
<span class="phase phase-1">Fase 1</span>
|
||||
</div>
|
||||
|
||||
<div class="tree">
|
||||
<div class="tree-root">Usuário (paciente@gmail.com)</div>
|
||||
<div> <span class="tree-branch">├──</span> <span class="tree-key">profiles.role</span> <span class="tree-val">'portal_user'</span></div>
|
||||
<div> <span class="tree-branch">└──</span> <span class="tree-comment">// sem memberships de tenant</span></div>
|
||||
<div> <span class="tree-comment">// acessa /portal/*</span></div>
|
||||
<div> <span class="tree-comment">// identidade global, não tenant</span></div>
|
||||
</div>
|
||||
|
||||
<div class="badges">
|
||||
<span class="badge badge-pink">role: portal_user</span>
|
||||
<span class="badge badge-gray">sem tenant</span>
|
||||
</div>
|
||||
|
||||
<p class="note">Acessa apenas o portal do paciente. Vê suas sessões, agenda e documentos. Nunca entra na área de tenant-app.</p>
|
||||
</div>
|
||||
|
||||
</div><!-- /grid -->
|
||||
|
||||
<!-- ── LEGEND ── -->
|
||||
<div class="legend">
|
||||
<h3>Legenda</h3>
|
||||
<div class="legend-grid">
|
||||
<div class="legend-item"><div class="legend-dot" style="background:#facc15"></div> Novo — Fase 1 (supervisor)</div>
|
||||
<div class="legend-item"><div class="legend-dot" style="background:#6ee7b7"></div> Existente — Fase 1</div>
|
||||
<div class="legend-item"><div class="legend-dot" style="background:#fb923c"></div> Planejado — Fase 2</div>
|
||||
<div class="legend-item"><div class="legend-dot" style="background:#a5b4fc"></div> Plataforma (sem tenant)</div>
|
||||
<div class="legend-item"><div class="legend-dot" style="background:#94a3b8"></div> profiles.role → identidade global</div>
|
||||
<div class="legend-item"><div class="legend-dot" style="background:#f9a8d4"></div> memberships.role → contexto de tenant</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── RESUMO TÉCNICO ── -->
|
||||
<div class="legend" style="margin-top: 1rem">
|
||||
<h3>Resumo Técnico — Como o Guard decide o menu</h3>
|
||||
<div style="font-family: monospace; font-size: .8rem; line-height: 2; color: #94a3b8;">
|
||||
<div><span style="color:#a5b4fc">profiles.role</span> = identidade global (saas_admin | tenant_member | portal_user)</div>
|
||||
<div><span style="color:#6ee7b7">memberships.role</span> = papel dentro do tenant (clinic_admin | therapist | supervisor | editor)</div>
|
||||
<div><span style="color:#f9a8d4">tenant.kind</span> = tipo do tenant (clinic | saas | supervisor) → define qual menu e contexto</div>
|
||||
<div><span style="color:#fde047">plans.target</span> = para quem é o plano (clinic | therapist | supervisor)</div>
|
||||
<div><span style="color:#fdba74">plans.max_supervisees</span> = limite de supervisionados (novo — Fase 1)</div>
|
||||
<div style="margin-top:.5rem; color: #475569">
|
||||
Entitlements: v_tenant_entitlements (plano do tenant) UNION v_user_entitlements (assinatura pessoal)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
556
docs/architecture/notification-reminders-system.md
Normal file
556
docs/architecture/notification-reminders-system.md
Normal file
@@ -0,0 +1,556 @@
|
||||
# Sistema de Lembretes Automáticos — WhatsApp, E-mail, SMS
|
||||
|
||||
> Agência PSI — Arquitetura completa
|
||||
> Data: 2026-03-21
|
||||
> Autor: Leonardo Nohama
|
||||
|
||||
---
|
||||
|
||||
## Sumário
|
||||
|
||||
1. [Decisão de Provedor WhatsApp](#1-decisão-de-provedor-whatsapp)
|
||||
2. [Modelagem do Banco de Dados](#2-modelagem-do-banco-de-dados)
|
||||
3. [Lógica de Agendamento (Cron + Edge Functions)](#3-lógica-de-agendamento)
|
||||
4. [Integração Evolution API (MVP)](#4-integração-evolution-api-mvp)
|
||||
5. [Integração API Oficial Meta (Escala)](#5-integração-api-oficial-meta)
|
||||
6. [Frontend: Telas de Configuração](#6-frontend-telas-de-configuração)
|
||||
7. [LGPD e Boas Práticas](#7-lgpd-e-boas-práticas)
|
||||
8. [Diagrama do Fluxo Completo](#8-diagrama-do-fluxo-completo)
|
||||
9. [Checklist de Produção](#9-checklist-de-produção)
|
||||
|
||||
---
|
||||
|
||||
## 1. Decisão de Provedor WhatsApp
|
||||
|
||||
### Comparativo de Provedores
|
||||
|
||||
| Critério | Evolution API | WPPConnect | Z-API (grátis) | Z-API Pro | Twilio (Meta oficial) | 360dialog (Meta oficial) | Zenvia (Meta oficial) |
|
||||
|----------|--------------|------------|----------------|-----------|----------------------|-------------------------|----------------------|
|
||||
| **Tipo** | Não-oficial (baileys) | Não-oficial | Não-oficial | Não-oficial (infra deles) | API Oficial Meta | API Oficial Meta | API Oficial Meta |
|
||||
| **Custo mensal** | R$ 0 (self-hosted) | R$ 0 (self-hosted) | R$ 0 (limite de msgs) | R$ 99–299/mês | ~R$ 0.30/msg (conversa) | €49/mês + msg | R$ 0.15–0.40/msg |
|
||||
| **Setup** | 2–4h (Docker) | 4–8h | 30min (SaaS) | 1h (SaaS) | 1–2 semanas (aprovação) | 3–5 dias | 3–5 dias |
|
||||
| **Risco de ban** | **MÉDIO-ALTO** | ALTO | MÉDIO | MÉDIO | **ZERO** | **ZERO** | **ZERO** |
|
||||
| **Templates Meta** | ❌ Não suporta | ❌ | ❌ | ❌ | ✅ Obrigatório | ✅ Obrigatório | ✅ Obrigatório |
|
||||
| **Webhooks status** | ✅ Completo | ✅ Parcial | ✅ | ✅ | ✅ Completo | ✅ Completo | ✅ Completo |
|
||||
| **Multi-instância** | ✅ Nativo | ❌ Manual | ❌ | ✅ | ✅ Via WABA | ✅ | ✅ |
|
||||
| **Uptime SLA** | Depende de você | Depende de você | 99.5% | 99.5% | 99.95% | 99.9% | 99.9% |
|
||||
| **Escalabilidade** | ~500 msgs/dia seguro | ~300 msgs/dia | ~200 msgs/dia | ~2000 msgs/dia | Ilimitado | Ilimitado | Ilimitado |
|
||||
|
||||
### Recomendação
|
||||
|
||||
**MVP (0–100 clínicas):** Evolution API self-hosted
|
||||
- Custo zero, setup rápido, suficiente para validar o produto
|
||||
- Cada terapeuta conecta seu próprio número via QR Code
|
||||
- Limite prático: ~500 mensagens/dia por número
|
||||
- Mitigação de ban: mensagens personalizadas (não genéricas), intervalos entre envios,
|
||||
máximo 2 lembretes por sessão
|
||||
|
||||
**Escala (100+ clínicas):** Migrar para API Oficial da Meta via 360dialog ou Twilio
|
||||
- Zero risco de banimento
|
||||
- Templates aprovados pela Meta = entrega garantida
|
||||
- Custo previsível por conversa (~R$ 0.25–0.40 por conversa de 24h)
|
||||
- Suporte a botões interativos (confirmar/cancelar)
|
||||
|
||||
**Estratégia de migração:** O sistema será projetado com abstração de provedor desde o início.
|
||||
A tabela `notification_channels` registra qual provedor cada tenant usa. Trocar de Evolution
|
||||
para Meta oficial = mudar o `provider` e credenciais, sem alterar a fila ou templates.
|
||||
|
||||
---
|
||||
|
||||
## 2. Modelagem do Banco de Dados
|
||||
|
||||
> **Nota de integração:** O sistema existente já possui:
|
||||
> - `email_templates_global` / `email_templates_tenant` → serão estendidos (não duplicados)
|
||||
> - `notifications` → continuam para notificações in-app (realtime)
|
||||
> - `profiles.notify_reminders` → será respeitado como opt-out global
|
||||
> - `TEMPLATE_CHANNELS` em emailTemplateConstants.js → já prevê whatsapp/sms
|
||||
|
||||
### Relação entre tabelas novas e existentes
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ EXISTENTES (não alterar) │
|
||||
│ │
|
||||
│ email_templates_global ──→ templates de email (11 seeds) │
|
||||
│ email_templates_tenant ──→ overrides por tenant/owner │
|
||||
│ notifications ──→ notificações in-app (realtime) │
|
||||
│ profiles ──→ notify_reminders, notify_system_email│
|
||||
│ agenda_eventos ──→ sessões com patient_id, inicio_em │
|
||||
│ patients ──→ nome_completo, telefone, email │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ NOVAS TABELAS │
|
||||
│ │
|
||||
│ notification_channels ──→ config WhatsApp/SMS por tenant │
|
||||
│ notification_templates ──→ templates multi-canal (wpp/sms) │
|
||||
│ notification_queue ──→ fila de envio │
|
||||
│ notification_logs ──→ histórico completo │
|
||||
│ notification_preferences ──→ opt-in/opt-out por paciente │
|
||||
│ notification_schedules ──→ regras de quando disparar │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Lógica de Agendamento
|
||||
|
||||
### Fluxo Completo
|
||||
|
||||
```
|
||||
pg_cron (a cada 5 min)
|
||||
│
|
||||
├──→ populate_notification_queue() ← PL/pgSQL function
|
||||
│ Busca agenda_eventos com inicio_em futuro,
|
||||
│ cruza com notification_schedules ativas,
|
||||
│ verifica notification_preferences do paciente,
|
||||
│ insere na notification_queue com idempotency_key
|
||||
│
|
||||
└──→ HTTP call → Edge Function: process-notification-queue
|
||||
│
|
||||
├── Busca itens pendentes (status = 'pendente', scheduled_at <= now())
|
||||
├── Marca como 'processando' (lock otimista via updated_at)
|
||||
├── Resolve variáveis do template
|
||||
├── Despacha para o provedor correto:
|
||||
│ ├── WhatsApp → Evolution API ou Meta API
|
||||
│ ├── Email → Resend / SendGrid / SMTP
|
||||
│ └── SMS → Zenvia / Twilio
|
||||
├── Atualiza status → 'enviado' ou 'falhou'
|
||||
├── Insere em notification_logs
|
||||
└── Em caso de falha: agenda retry exponencial
|
||||
```
|
||||
|
||||
### Retry Exponencial
|
||||
|
||||
```
|
||||
Tentativa 1: imediato
|
||||
Tentativa 2: +5 minutos
|
||||
Tentativa 3: +15 minutos
|
||||
Tentativa 4: +60 minutos
|
||||
Tentativa 5: +4 horas
|
||||
Máximo: 5 tentativas → marca como 'falhou' definitivamente
|
||||
```
|
||||
|
||||
### Prevenção de Duplicatas
|
||||
|
||||
1. **Idempotency key** = `{agenda_evento_id}:{schedule_key}:{canal}:{data_sessao}`
|
||||
2. **UNIQUE constraint** na notification_queue sobre idempotency_key
|
||||
3. **Lock otimista** no processamento: `UPDATE ... WHERE status = 'pendente' AND updated_at = ?`
|
||||
4. **pg_cron não overlap**: usa `pg_try_advisory_lock()` no populate
|
||||
|
||||
---
|
||||
|
||||
## 4. Integração Evolution API (MVP)
|
||||
|
||||
### Setup Docker
|
||||
|
||||
```yaml
|
||||
# docker-compose.evolution.yml
|
||||
version: '3.8'
|
||||
services:
|
||||
evolution-api:
|
||||
image: atendai/evolution-api:latest
|
||||
ports:
|
||||
- "8080:8080"
|
||||
environment:
|
||||
- AUTHENTICATION_API_KEY=sua_chave_global_aqui
|
||||
- DATABASE_PROVIDER=postgresql
|
||||
- DATABASE_CONNECTION_URI=postgresql://user:pass@host:5432/evolution
|
||||
- WEBHOOK_GLOBAL_URL=https://seu-dominio.com/api/webhooks/evolution
|
||||
- WEBHOOK_GLOBAL_ENABLED=true
|
||||
- WEBHOOK_EVENTS_STATUS_INSTANCE=true
|
||||
- WEBHOOK_EVENTS_MESSAGES_UPSERT=true
|
||||
- WEBHOOK_EVENTS_SEND_MESSAGE=true
|
||||
volumes:
|
||||
- evolution_data:/evolution/store
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
evolution_data:
|
||||
```
|
||||
|
||||
### Endpoints Necessários
|
||||
|
||||
```
|
||||
Base URL: https://evolution.seudominio.com
|
||||
|
||||
POST /instance/create → criar instância para o tenant
|
||||
GET /instance/connect/{name} → obter QR Code para conectar número
|
||||
GET /instance/connectionState/{name} → verificar status da conexão
|
||||
POST /message/sendText/{name} → enviar mensagem de texto
|
||||
POST /message/sendMedia/{name} → enviar com mídia (opcional)
|
||||
DELETE /instance/delete/{name} → remover instância
|
||||
```
|
||||
|
||||
### Payload de Envio
|
||||
|
||||
```json
|
||||
// POST /message/sendText/{instance_name}
|
||||
{
|
||||
"number": "5516999887766",
|
||||
"text": "Olá Ana Clara! 👋\n\nLembrete: você tem sessão amanhã, 21/03, às 14:00 com Dra. Beatriz Costa.\n\n📍 Online via Google Meet\n🔗 https://meet.google.com/abc-defg-hij\n\nPara confirmar, responda OK.\nPara cancelar, responda CANCELAR.\n\nAgência PSI"
|
||||
}
|
||||
```
|
||||
|
||||
### Webhook de Status
|
||||
|
||||
```json
|
||||
// POST /api/webhooks/evolution (recebido do Evolution)
|
||||
{
|
||||
"event": "messages.update",
|
||||
"instance": "clinica_abc",
|
||||
"data": {
|
||||
"key": {
|
||||
"remoteJid": "5516999887766@s.whatsapp.net",
|
||||
"id": "3EB0A0B6F..."
|
||||
},
|
||||
"update": {
|
||||
"status": 3 // 1=pendente, 2=enviado ao servidor, 3=entregue, 4=lido
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Credenciais no Supabase
|
||||
|
||||
Armazenadas na tabela `notification_channels.credentials` como JSONB criptografado:
|
||||
|
||||
```json
|
||||
{
|
||||
"api_url": "https://evolution.seudominio.com",
|
||||
"api_key": "chave_global_evolution",
|
||||
"instance_name": "clinica_dr_beatriz",
|
||||
"connected_number": "5516999887766",
|
||||
"connection_status": "open"
|
||||
}
|
||||
```
|
||||
|
||||
> A criptografia das credenciais usa `pgcrypto` com chave armazenada como
|
||||
> variável de ambiente do Supabase (Vault). Veja a função SQL `encrypt_credentials()`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Integração API Oficial Meta
|
||||
|
||||
### Template de Lembrete para Aprovação
|
||||
|
||||
Nome: `session_reminder_v1`
|
||||
Categoria: `UTILITY`
|
||||
Idioma: `pt_BR`
|
||||
|
||||
```
|
||||
HEADER: 📋 Lembrete de Sessão
|
||||
BODY: Olá {{1}}! Sua sessão com {{2}} está agendada para {{3}} às {{4}}.
|
||||
Modalidade: {{5}}
|
||||
BUTTONS:
|
||||
[quick_reply] ✅ Confirmar presença
|
||||
[quick_reply] ❌ Preciso cancelar
|
||||
FOOTER: Agência PSI — Tecnologia aplicada à escuta
|
||||
```
|
||||
|
||||
### Envio via Graph API
|
||||
|
||||
```
|
||||
POST https://graph.facebook.com/v19.0/{phone_number_id}/messages
|
||||
Authorization: Bearer {access_token}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"messaging_product": "whatsapp",
|
||||
"to": "5516999887766",
|
||||
"type": "template",
|
||||
"template": {
|
||||
"name": "session_reminder_v1",
|
||||
"language": { "code": "pt_BR" },
|
||||
"components": [
|
||||
{
|
||||
"type": "body",
|
||||
"parameters": [
|
||||
{ "type": "text", "text": "Ana Clara" },
|
||||
{ "type": "text", "text": "Dra. Beatriz Costa" },
|
||||
{ "type": "text", "text": "21/03/2026" },
|
||||
{ "type": "text", "text": "14:00" },
|
||||
{ "type": "text", "text": "Online" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Webhook de Status (Meta)
|
||||
|
||||
```json
|
||||
// POST /api/webhooks/meta-whatsapp
|
||||
{
|
||||
"entry": [{
|
||||
"changes": [{
|
||||
"value": {
|
||||
"statuses": [{
|
||||
"id": "wamid.HBgN...",
|
||||
"status": "delivered", // sent, delivered, read, failed
|
||||
"timestamp": "1711036800",
|
||||
"recipient_id": "5516999887766",
|
||||
"errors": []
|
||||
}]
|
||||
}
|
||||
}]
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Frontend: Telas de Configuração
|
||||
|
||||
### 6.1 Configuração de Canal (`ConfiguracoesCanaisPage.vue`)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ 📡 Canais de Notificação │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────┐ │
|
||||
│ │ 💬 WhatsApp [Ativo ✅] │ │
|
||||
│ │ Provedor: Evolution API │ │
|
||||
│ │ Número: +55 16 99988-7766 │ │
|
||||
│ │ Status: 🟢 Conectado │ │
|
||||
│ │ │ │
|
||||
│ │ [Reconectar] [Ver QR Code] [Testar] │ │
|
||||
│ └──────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────┐ │
|
||||
│ │ 📧 E-mail [Ativo ✅] │ │
|
||||
│ │ Provedor: Resend │ │
|
||||
│ │ Remetente: clinica@drbeat... │ │
|
||||
│ │ Status: 🟢 Verificado │ │
|
||||
│ │ │ │
|
||||
│ │ [Configurar SMTP] [Testar] │ │
|
||||
│ └──────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────┐ │
|
||||
│ │ 📱 SMS [Inativo ⬜] │ │
|
||||
│ │ Não configurado │ │
|
||||
│ │ │ │
|
||||
│ │ [Ativar] │ │
|
||||
│ └──────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 6.2 Templates (`ConfiguracoesNotifTemplatesPage.vue`)
|
||||
|
||||
- Lista de templates por domínio (Sessão, Triagem, Sistema)
|
||||
- Preview ao vivo com variáveis mock (já existe no sistema)
|
||||
- Cada template pode ser personalizado por canal (email / whatsapp / sms)
|
||||
- Herança: se o tenant não customizou, mostra o template global com badge "Padrão"
|
||||
|
||||
### 6.3 Regras de Envio (`ConfiguracoesNotifRegrasPage.vue`)
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────┐
|
||||
│ ⏰ Regras de Envio de Lembretes │
|
||||
├──────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Lembrete de Sessão │
|
||||
│ ├── 24 horas antes [WhatsApp ✅] [Email ✅] │
|
||||
│ ├── 2 horas antes [WhatsApp ✅] [Email ⬜] │
|
||||
│ └── 30 min antes [WhatsApp ⬜] [Email ⬜] │
|
||||
│ │
|
||||
│ Confirmação de Sessão │
|
||||
│ ├── Imediata (ao criar) [Email ✅] │
|
||||
│ └── Imediata [WhatsApp ✅] │
|
||||
│ │
|
||||
│ Cancelamento │
|
||||
│ └── Imediata [WhatsApp ✅] [Email ✅]│
|
||||
│ │
|
||||
│ Boas-vindas (novo paciente) │
|
||||
│ └── Imediata [WhatsApp ✅] [Email ✅]│
|
||||
│ │
|
||||
│ ⚙️ Horário permitido: 08:00 – 20:00 │
|
||||
│ 📅 Não enviar: Domingos e feriados │
|
||||
│ │
|
||||
│ [Salvar configurações] │
|
||||
└──────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 6.4 Logs de Envio (`ConfiguracoesNotifLogsPage.vue`)
|
||||
|
||||
- Tabela paginada com filtros por canal, status, período
|
||||
- Detalhes por envio: template usado, variáveis resolvidas, resposta do provedor
|
||||
- Estatísticas: total enviado, entregue, lido, falhou (últimos 30 dias)
|
||||
- Export CSV
|
||||
|
||||
### 6.5 Opt-out pelo Paciente
|
||||
|
||||
- **WhatsApp:** Paciente responde "SAIR" → webhook captura → atualiza `notification_preferences`
|
||||
- **Email:** Link "Cancelar inscrição" no rodapé → página pública de opt-out
|
||||
- **Portal do paciente (futuro):** Toggle na área logada do paciente
|
||||
|
||||
---
|
||||
|
||||
## 7. LGPD e Boas Práticas
|
||||
|
||||
### Coleta de Consentimento
|
||||
|
||||
1. **Cadastro externo** (CadastroPacienteExterno.vue): já tem checkbox LGPD
|
||||
2. **Cadastro pelo terapeuta**: adicionar checkbox "Paciente autoriza receber lembretes por WhatsApp/E-mail"
|
||||
3. **Primeiro lembrete**: incluir mensagem "Responda SAIR a qualquer momento para parar de receber mensagens"
|
||||
|
||||
### Armazenamento Seguro
|
||||
|
||||
- Telefone do paciente: armazenado na tabela `patients.telefone` (já existe)
|
||||
- Credenciais do provedor: `notification_channels.credentials` criptografado com `pgcrypto`
|
||||
- Chave de criptografia: Supabase Vault (variável de ambiente, nunca no código)
|
||||
|
||||
### Retenção de Logs
|
||||
|
||||
- `notification_logs`: reter por **2 anos** (exigência legal para comprovação de comunicação)
|
||||
- `notification_queue`: limpar itens processados após **90 dias** (via pg_cron)
|
||||
- `notification_preferences`: manter enquanto o paciente estiver ativo
|
||||
|
||||
### Opt-out Imediato
|
||||
|
||||
- Resposta "SAIR" no WhatsApp → webhook → `notification_preferences.whatsapp_opt_in = false`
|
||||
- Efeito imediato: todas as mensagens pendentes na fila para aquele paciente são canceladas
|
||||
- Trigger SQL: ao atualizar opt-out, cancela itens pendentes na queue
|
||||
|
||||
---
|
||||
|
||||
## 8. Diagrama do Fluxo Completo
|
||||
|
||||
```
|
||||
┌──────────────┐
|
||||
│ agenda_eventos│ ← terapeuta cria/edita sessão
|
||||
└──────┬───────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────┐
|
||||
│ pg_cron: populate_notification_queue │ ← roda a cada 5 min
|
||||
│ │
|
||||
│ 1. Busca sessões com inicio_em │
|
||||
│ entre agora e +48h │
|
||||
│ 2. Cruza com notification_schedules │
|
||||
│ (ex: 24h antes, 2h antes) │
|
||||
│ 3. Verifica: │
|
||||
│ - notification_preferences (opt-in)│
|
||||
│ - notification_channels (canal ativo)│
|
||||
│ - profiles.notify_reminders │
|
||||
│ - agenda_eventos.status ≠ cancelado│
|
||||
│ 4. Gera idempotency_key │
|
||||
│ 5. INSERT INTO notification_queue │
|
||||
│ ON CONFLICT DO NOTHING │
|
||||
└──────────────┬───────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────┐
|
||||
│ pg_cron: chama Edge Function │ ← roda a cada 5 min (offset 2min)
|
||||
│ POST /functions/v1/process-notif-queue│
|
||||
└──────────────┬───────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────┐
|
||||
│ Edge Function: process-notif-queue │
|
||||
│ │
|
||||
│ 1. SELECT ... FROM notification_queue│
|
||||
│ WHERE status = 'pendente' │
|
||||
│ AND scheduled_at <= now() │
|
||||
│ LIMIT 50 │
|
||||
│ FOR UPDATE SKIP LOCKED │
|
||||
│ │
|
||||
│ 2. Para cada item: │
|
||||
│ a. Marca 'processando' │
|
||||
│ b. Resolve template + variáveis │
|
||||
│ c. Despacha para provedor: │
|
||||
│ ┌──────────────────────┐ │
|
||||
│ │ channel = 'whatsapp' │ │
|
||||
│ │ → Evolution API │ │
|
||||
│ │ ou Meta Graph API │ │
|
||||
│ ├──────────────────────┤ │
|
||||
│ │ channel = 'email' │ │
|
||||
│ │ → Resend / SMTP │ │
|
||||
│ ├──────────────────────┤ │
|
||||
│ │ channel = 'sms' │ │
|
||||
│ │ → Zenvia / Twilio │ │
|
||||
│ └──────────────────────┘ │
|
||||
│ d. Atualiza status │
|
||||
│ e. INSERT notification_logs │
|
||||
│ │
|
||||
│ 3. Itens com falha: │
|
||||
│ attempts += 1 │
|
||||
│ next_retry_at = exponential │
|
||||
│ status = attempts >= 5 │
|
||||
│ ? 'falhou' : 'pendente' │
|
||||
└──────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────┐
|
||||
│ Webhook Handler │
|
||||
│ POST /functions/v1/notif-webhook │
|
||||
│ │
|
||||
│ Recebe status do provedor: │
|
||||
│ - Evolution: messages.update │
|
||||
│ - Meta: webhook de status │
|
||||
│ - Email: bounce/delivery events │
|
||||
│ │
|
||||
│ Atualiza notification_logs: │
|
||||
│ - delivered_at, read_at, failed_at │
|
||||
│ - provider_status, provider_response │
|
||||
│ │
|
||||
│ Se resposta = "SAIR": │
|
||||
│ → UPDATE notification_preferences │
|
||||
│ SET whatsapp_opt_in = false │
|
||||
│ → CANCEL pendentes na queue │
|
||||
└──────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Checklist de Produção
|
||||
|
||||
### Infraestrutura
|
||||
- [ ] Subir Evolution API (Docker) em VPS dedicada
|
||||
- [ ] Configurar domínio + SSL para Evolution API
|
||||
- [ ] Configurar Supabase Vault com chave de criptografia
|
||||
- [ ] Habilitar extensão `pgcrypto` no Supabase
|
||||
- [ ] Habilitar extensão `pg_cron` no Supabase
|
||||
- [ ] Configurar DNS para webhook (ex: `webhooks.agenciapsi.com.br`)
|
||||
|
||||
### Banco de Dados
|
||||
- [ ] Rodar migration: tabelas de notificação
|
||||
- [ ] Rodar migration: functions PL/pgSQL (populate queue, encrypt/decrypt)
|
||||
- [ ] Configurar pg_cron jobs (populate + process)
|
||||
- [ ] Verificar RLS policies em todas as tabelas
|
||||
- [ ] Seed: notification_schedules padrão (24h, 2h)
|
||||
- [ ] Seed: notification_templates padrão (whatsapp + sms)
|
||||
|
||||
### Edge Functions
|
||||
- [ ] Deploy: `process-notif-queue`
|
||||
- [ ] Deploy: `notif-webhook`
|
||||
- [ ] Configurar secrets: `EVOLUTION_API_KEY`, `ENCRYPTION_KEY`
|
||||
- [ ] Testar com payload simulado
|
||||
|
||||
### Frontend
|
||||
- [ ] Tela de configuração de canais
|
||||
- [ ] Tela de templates (estender ConfiguracoesEmailTemplatesPage existente)
|
||||
- [ ] Tela de regras de envio
|
||||
- [ ] Tela de logs
|
||||
- [ ] Adicionar consentimento no cadastro de paciente
|
||||
- [ ] Adicionar opt-out no rodapé de emails
|
||||
|
||||
### Testes
|
||||
- [ ] Teste E2E: criar sessão → verificar queue populada → verificar envio
|
||||
- [ ] Teste: opt-out WhatsApp → verificar cancelamento na queue
|
||||
- [ ] Teste: retry após falha → verificar exponential backoff
|
||||
- [ ] Teste: idempotency → rodar populate 2x → verificar sem duplicatas
|
||||
- [ ] Teste: tenant isolation → verificar RLS
|
||||
- [ ] Teste de carga: 1000 mensagens na queue → medir throughput
|
||||
|
||||
### Monitoramento
|
||||
- [ ] Alerta se queue > 500 itens pendentes
|
||||
- [ ] Alerta se taxa de falha > 10% em 1h
|
||||
- [ ] Dashboard de métricas (envios/dia, taxa de entrega, tempo médio de processamento)
|
||||
- [ ] Log de erros no Supabase Logs
|
||||
|
||||
---
|
||||
|
||||
*Documento gerado como parte da arquitetura do sistema Agência PSI.*
|
||||
*As implementações SQL e JavaScript estão nos arquivos separados referenciados abaixo.*
|
||||
672
docs/billing/Agencia_PSI_Billing_Mestre_v2_0.html
Normal file
672
docs/billing/Agencia_PSI_Billing_Mestre_v2_0.html
Normal file
@@ -0,0 +1,672 @@
|
||||
<!doctype html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Documento Mestre — Billing (Plans, Pricing, Subscriptions, Entitlements) v2.0 | Agência PSI</title>
|
||||
<style>
|
||||
:root{
|
||||
--bg0:#f6f8fc;
|
||||
--bg1:#eef2f8;
|
||||
--panel:rgba(255,255,255,.78);
|
||||
--panel2:rgba(255,255,255,.92);
|
||||
--border:rgba(15,23,42,.10);
|
||||
--text:rgba(15,23,42,.92);
|
||||
--muted:rgba(15,23,42,.70);
|
||||
--muted2:rgba(15,23,42,.56);
|
||||
--accent:#2563eb;
|
||||
--accent2:#4f46e5;
|
||||
--warn:#b45309;
|
||||
--danger:#b91c1c;
|
||||
--ok:#047857;
|
||||
--shadow: 0 18px 60px rgba(2,6,23,.10);
|
||||
--radius: 16px;
|
||||
--radius2: 22px;
|
||||
--mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
||||
}
|
||||
*{box-sizing:border-box;}
|
||||
body{
|
||||
margin:0;
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial;
|
||||
background: radial-gradient(1200px 600px at 10% 0%, rgba(79,70,229,.10), transparent 55%),
|
||||
radial-gradient(1100px 500px at 90% 10%, rgba(37,99,235,.10), transparent 55%),
|
||||
linear-gradient(180deg, var(--bg0), var(--bg1));
|
||||
color:var(--text);
|
||||
}
|
||||
a{color:inherit; text-decoration:none;}
|
||||
a:hover{text-decoration:underline;}
|
||||
.layout{
|
||||
display:grid;
|
||||
grid-template-columns: 320px 1fr;
|
||||
gap: 20px;
|
||||
max-width: 1320px;
|
||||
margin: 0 auto;
|
||||
padding: 28px 18px 42px;
|
||||
}
|
||||
header{
|
||||
grid-column: 1 / -1;
|
||||
padding: 18px;
|
||||
border: 1px solid var(--border);
|
||||
background: linear-gradient(180deg, rgba(255,255,255,.92), rgba(255,255,255,.72));
|
||||
border-radius: var(--radius2);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.kicker{
|
||||
font-size:12px;
|
||||
letter-spacing:.08em;
|
||||
text-transform:uppercase;
|
||||
color:var(--muted2);
|
||||
margin:0 0 8px;
|
||||
}
|
||||
h1{
|
||||
margin:0 0 8px;
|
||||
font-size:30px;
|
||||
letter-spacing:-0.02em;
|
||||
}
|
||||
.subtitle{
|
||||
margin:0;
|
||||
color:var(--muted);
|
||||
max-width:980px;
|
||||
line-height:1.55;
|
||||
font-size:14px;
|
||||
}
|
||||
.meta-row{
|
||||
margin-top:12px;
|
||||
display:flex;
|
||||
flex-wrap:wrap;
|
||||
gap:10px;
|
||||
align-items:center;
|
||||
}
|
||||
.pill{
|
||||
display:inline-flex;
|
||||
align-items:center;
|
||||
gap:8px;
|
||||
padding:8px 12px;
|
||||
border-radius:999px;
|
||||
border:1px solid var(--border);
|
||||
background:rgba(255,255,255,.72);
|
||||
color:var(--muted);
|
||||
font-size:12px;
|
||||
}
|
||||
.dot{
|
||||
width:8px;height:8px;border-radius:50%;
|
||||
background:var(--accent);
|
||||
box-shadow:0 0 0 4px rgba(37,99,235,.12);
|
||||
}
|
||||
aside{
|
||||
position:sticky;
|
||||
top:18px;
|
||||
align-self:start;
|
||||
border:1px solid var(--border);
|
||||
background:var(--panel2);
|
||||
border-radius:var(--radius);
|
||||
box-shadow:var(--shadow);
|
||||
overflow:hidden;
|
||||
}
|
||||
.toc-head{
|
||||
padding:14px;
|
||||
border-bottom:1px solid var(--border);
|
||||
background:rgba(15,23,42,.02);
|
||||
}
|
||||
.toc-title{ margin:0 0 6px; font-weight:700; font-size:14px; }
|
||||
.toc-sub{ margin:0; color:var(--muted); font-size:12px; line-height:1.45; }
|
||||
nav{ padding: 10px 6px 14px; }
|
||||
nav a{
|
||||
display:block;
|
||||
padding:10px 12px;
|
||||
margin:4px 6px;
|
||||
border-radius:12px;
|
||||
color:var(--muted);
|
||||
font-size:13px;
|
||||
border:1px solid transparent;
|
||||
}
|
||||
nav a:hover{
|
||||
background:rgba(37,99,235,.06);
|
||||
border-color:rgba(37,99,235,.12);
|
||||
color:var(--text);
|
||||
text-decoration:none;
|
||||
}
|
||||
.nav-sec{
|
||||
margin:10px 12px 6px;
|
||||
color:var(--muted2);
|
||||
font-size:11px;
|
||||
letter-spacing:.08em;
|
||||
text-transform:uppercase;
|
||||
}
|
||||
main{
|
||||
border:1px solid var(--border);
|
||||
background:var(--panel);
|
||||
border-radius:var(--radius2);
|
||||
box-shadow:var(--shadow);
|
||||
overflow:hidden;
|
||||
}
|
||||
.content{ padding: 18px 18px 22px; }
|
||||
.section{
|
||||
padding: 18px;
|
||||
border:1px solid var(--border);
|
||||
border-radius: var(--radius2);
|
||||
background: rgba(255,255,255,.80);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.section h2{
|
||||
margin:0 0 10px;
|
||||
font-size:18px;
|
||||
letter-spacing:-0.01em;
|
||||
}
|
||||
.section h3{
|
||||
margin:14px 0 8px;
|
||||
font-size:14px;
|
||||
}
|
||||
.section p, .section li{
|
||||
color:var(--muted);
|
||||
line-height:1.65;
|
||||
font-size:13.5px;
|
||||
}
|
||||
.grid{
|
||||
display:grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
@media (max-width: 980px){
|
||||
.layout{ grid-template-columns: 1fr; }
|
||||
aside{ position:relative; top:auto; }
|
||||
.grid{ grid-template-columns: 1fr; }
|
||||
}
|
||||
.callout{
|
||||
border-radius: 16px;
|
||||
padding: 12px 12px 12px 14px;
|
||||
border:1px solid var(--border);
|
||||
background: rgba(15,23,42,.02);
|
||||
margin-top: 10px;
|
||||
}
|
||||
.callout strong{color:var(--text);}
|
||||
.callout.ok{ border-left: 4px solid var(--ok); background: rgba(4,120,87,.06); }
|
||||
.callout.warn{ border-left: 4px solid var(--warn); background: rgba(180,83,9,.08); }
|
||||
.callout.danger{ border-left: 4px solid var(--danger); background: rgba(185,28,28,.08); }
|
||||
.callout.info{ border-left: 4px solid var(--accent); background: rgba(37,99,235,.08); }
|
||||
pre{
|
||||
margin: 10px 0 0;
|
||||
padding: 14px;
|
||||
background: #0b1220;
|
||||
color:#e2e8f0;
|
||||
border-radius: 16px;
|
||||
overflow:auto;
|
||||
border: 1px solid rgba(226,232,240,.08);
|
||||
}
|
||||
code{ font-family: var(--mono); font-size:12.5px; }
|
||||
.kbd{
|
||||
font-family: var(--mono);
|
||||
font-size: 12px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border);
|
||||
background: rgba(255,255,255,.65);
|
||||
color: var(--text);
|
||||
}
|
||||
.hr{
|
||||
height:1px;
|
||||
background: var(--border);
|
||||
margin: 12px 0;
|
||||
}
|
||||
footer{
|
||||
margin-top: 12px;
|
||||
padding: 14px 18px 18px;
|
||||
color: var(--muted2);
|
||||
font-size: 12px;
|
||||
border-top: 1px solid var(--border);
|
||||
background: rgba(255,255,255,.70);
|
||||
}
|
||||
.small{ font-size:12px; color:var(--muted2); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="layout">
|
||||
<header>
|
||||
<p class="kicker">Documento Mestre • Billing • Agência PSI</p>
|
||||
<h1>Plans, Pricing, Subscriptions & Entitlements — v2.0</h1>
|
||||
<p class="subtitle">
|
||||
Documento institucional do domínio <strong>Billing</strong>. Unifica: catálogo de planos, preços vigentes,
|
||||
assinatura (clínica/terapeuta), guardrails e entitlements (features + limits). Este material é pensado para
|
||||
reduzir regressões e orientar o operador/dev quando algo “parecer impossível” (ex.: corrigir plano core sem
|
||||
desativar triggers).
|
||||
</p>
|
||||
<div class="meta-row">
|
||||
<span class="pill"><span class="dot"></span><strong>Estado:</strong> operacional (MVP)</span>
|
||||
<span class="pill"><strong>Atualizado:</strong> 2026-03-01 10:43:18 UTC</span>
|
||||
<span class="pill"><strong>Padrão:</strong> Supabase + Postgres + Vue/PrimeVue</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<aside>
|
||||
<div class="toc-head">
|
||||
<div class="toc-title">Sumário</div>
|
||||
<p class="toc-sub">Links rápidos para leitura e execução (SQL/Seeder/Views).</p>
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<div class="nav-sec">Visão geral</div>
|
||||
<a href="#01">1. Contexto e objetivos</a>
|
||||
<a href="#02">2. Entidades e conceitos</a>
|
||||
|
||||
<div class="nav-sec">Plans & Pricing</div>
|
||||
<a href="#03">3. Tabela plans e planos core</a>
|
||||
<a href="#04">4. Pricing e vigência (plan_prices + views)</a>
|
||||
|
||||
<div class="nav-sec">Subscriptions</div>
|
||||
<a href="#05">5. Subscriptions: schema e regras</a>
|
||||
<a href="#06">6. Views: active_subscription</a>
|
||||
<a href="#07">7. Operações: change, cancel, reactivate</a>
|
||||
<a href="#08">8. Auditoria: subscription_events</a>
|
||||
|
||||
<div class="nav-sec">Entitlements</div>
|
||||
<a href="#09">9. Features + plan_features</a>
|
||||
<a href="#10">10. Views de entitlements (com limits)</a>
|
||||
|
||||
<div class="nav-sec">Guardrails</div>
|
||||
<a href="#11">11. Triggers de proteção</a>
|
||||
<a href="#12">12. Correção segura de plano core (bypass controlado)</a>
|
||||
|
||||
<div class="nav-sec">Seeders</div>
|
||||
<a href="#13">13. Seeder idempotente: features + plan_features</a>
|
||||
<a href="#14">14. Seeder idempotente: subscription de teste</a>
|
||||
|
||||
<div class="nav-sec">Front-end</div>
|
||||
<a href="#15">15. Padrões de UI e telas (Subscriptions / Eventos)</a>
|
||||
|
||||
<div class="nav-sec">Apêndices</div>
|
||||
<a href="#16">16. Troubleshooting (erros reais)</a>
|
||||
<a href="#17">17. Checklist de validação</a>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<main>
|
||||
<div class="content">
|
||||
|
||||
<section class="section" id="01">
|
||||
<h2>1. Contexto e objetivos</h2>
|
||||
<p>
|
||||
O MVP do SaaS precisa garantir que o sistema “respeite o plano”. Para isso, o domínio Billing opera em camadas:
|
||||
<strong>Plans</strong> (catálogo), <strong>Pricing</strong> (vigência), <strong>Subscriptions</strong> (plano vigente por tenant/user)
|
||||
e <strong>Entitlements</strong> (features + limites).
|
||||
</p>
|
||||
<div class="callout info">
|
||||
<strong>Regra de ouro:</strong> o front nunca deve “inferir plano” por role. O plano vigente vem de <code>subscriptions</code>
|
||||
e os limites/flags vêm de <code>plan_features</code>.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="02">
|
||||
<h2>2. Entidades e conceitos</h2>
|
||||
<div class="grid">
|
||||
<div class="callout">
|
||||
<strong>plans</strong>
|
||||
<p>Catálogo de planos (core e custom). Guarda <code>key</code>, <code>target</code>, preço base e metadados.</p>
|
||||
</div>
|
||||
<div class="callout">
|
||||
<strong>plan_prices</strong>
|
||||
<p>Preço com vigência. Preço vigente: <span class="kbd">is_active=true</span> e <span class="kbd">active_to is null</span>.</p>
|
||||
</div>
|
||||
<div class="callout">
|
||||
<strong>subscriptions</strong>
|
||||
<p>Assinatura ativa por tenant (clínica) ou por user (terapeuta). A view escolhe a mais recente por owner.</p>
|
||||
</div>
|
||||
<div class="callout">
|
||||
<strong>features / plan_features</strong>
|
||||
<p>Mapa de capabilities e limites (<code>limits jsonb</code>). É daqui que o front deve “gatear” menus/ações.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="03">
|
||||
<h2>3. Tabela plans e planos core</h2>
|
||||
<p><strong>Planos core do MVP</strong> (devem existir e permanecer): <code>clinic_free</code>, <code>clinic_pro</code>, <code>therapist_free</code>, <code>therapist_pro</code>.</p>
|
||||
<pre><code>-- estrutura confirmada (resumo)
|
||||
-- plans (public)
|
||||
-- id, key, name, description, is_active, price_cents, currency, billing_interval, target</code></pre>
|
||||
|
||||
<div class="callout warn">
|
||||
<strong>Observação importante:</strong> planos core têm guardrails: não podem ser deletados e sua <code>key</code> não pode ser alterada.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="04">
|
||||
<h2>4. Pricing e vigência</h2>
|
||||
<p>
|
||||
A UI pública de preços deve consumir <code>v_public_pricing</code>. A vigência de preço vem de <code>plan_prices</code>:
|
||||
preço vigente é aquele com <code>is_active=true</code> e <code>active_to is null</code>. Para planos FREE, a UI pode exibir “Grátis”
|
||||
mesmo sem registro em <code>plan_prices</code>.
|
||||
</p>
|
||||
<div class="callout info">
|
||||
<strong>Boas práticas:</strong> a tela pública não deve depender do schema “cru”. Mantenha a view como contrato.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="05">
|
||||
<h2>5. Subscriptions: schema e regras</h2>
|
||||
<p>Schema confirmado via <code>information_schema</code> e constraints:</p>
|
||||
<pre><code>-- subscriptions (public) - colunas relevantes
|
||||
id uuid primary key default gen_random_uuid()
|
||||
tenant_id uuid null
|
||||
user_id uuid null
|
||||
plan_id uuid not null references plans(id) on delete restrict
|
||||
status text not null default 'active'
|
||||
"interval" text null check ("interval" in ('month','year'))
|
||||
current_period_start timestamptz null
|
||||
current_period_end timestamptz null
|
||||
plan_key text null
|
||||
provider text not null default 'manual'
|
||||
source text not null default 'manual'
|
||||
started_at timestamptz not null default now()
|
||||
created_at timestamptz not null default now()
|
||||
updated_at timestamptz not null default now()</code></pre>
|
||||
|
||||
<div class="callout ok">
|
||||
<strong>Modelagem:</strong> clínica → usa <code>tenant_id</code>. Terapeuta → usa <code>user_id</code> (com <code>tenant_id</code> nulo).
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="06">
|
||||
<h2>6. View: v_tenant_active_subscription</h2>
|
||||
<p>
|
||||
Esta view define “o plano vigente” do tenant. Regra: status <code>active</code> e período ainda válido.
|
||||
Escolhe a assinatura mais recente por tenant (created_at DESC).
|
||||
</p>
|
||||
<pre><code>select distinct on (tenant_id)
|
||||
tenant_id,
|
||||
plan_id,
|
||||
plan_key,
|
||||
"interval",
|
||||
status,
|
||||
current_period_start,
|
||||
current_period_end,
|
||||
created_at
|
||||
from subscriptions s
|
||||
where tenant_id is not null
|
||||
and status = 'active'
|
||||
and (current_period_end is null or current_period_end > now())
|
||||
order by tenant_id, created_at desc;</code></pre>
|
||||
|
||||
<div class="callout info">
|
||||
<strong>Diagnóstico rápido:</strong> se views de entitlements estiverem “vazias”, primeiro verifique se existe subscription ativa nesta view.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="07">
|
||||
<h2>7. Operações de assinatura: change / cancel / reactivate</h2>
|
||||
<p>
|
||||
O front chama RPCs, mantendo a regra de ouro: “a verdade vem do banco”.
|
||||
Depois de operar, a tela recarrega para refletir o estado real.
|
||||
</p>
|
||||
<pre><code>-- RPCs usadas no front
|
||||
-- change_subscription_plan(p_subscription_id uuid, p_new_plan_id uuid)
|
||||
-- cancel_subscription(p_subscription_id uuid)
|
||||
-- reactivate_subscription(p_subscription_id uuid)</code></pre>
|
||||
<div class="callout warn">
|
||||
<strong>Nota:</strong> se o RPC atualizar apenas <code>plan_id</code>, é recomendável manter <code>plan_key</code> e <code>interval</code>
|
||||
consistentes (quando for relevante), para facilitar auditoria e debugging.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="08">
|
||||
<h2>8. Auditoria: subscription_events</h2>
|
||||
<p>
|
||||
Tela “Histórico de assinaturas” é read-only e mostra até 500 eventos mais recentes. Eventos típicos:
|
||||
<code>plan_changed</code>, <code>canceled</code>, <code>reactivated</code>.
|
||||
</p>
|
||||
<div class="callout info">
|
||||
<strong>UX operacional:</strong> o histórico deve permitir navegar de volta para o owner (Subscriptions) via query <code>?q=clinic:<uuid></code>.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="09">
|
||||
<h2>9. Features e plan_features</h2>
|
||||
<p>O MVP já possui <code>features</code> com keys (ex.: <code>online_scheduling</code>, <code>reports_basic</code>, etc.) e tabela <code>plan_features</code>:</p>
|
||||
<pre><code>-- plan_features(plan_id uuid not null, feature_id uuid not null,
|
||||
-- enabled boolean not null default true, limits jsonb null, created_at timestamptz default now())
|
||||
-- PK: (plan_id, feature_id)
|
||||
-- FK: feature_id → features(id) ON DELETE CASCADE
|
||||
-- FK: plan_id → plans(id) ON DELETE CASCADE</code></pre>
|
||||
|
||||
<div class="callout ok">
|
||||
<strong>Importante:</strong> <code>limits</code> é um contrato com o front. Ex.: <code>{"max_patients":30}</code>, <code>{"sessions_per_month":40}</code>.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="10">
|
||||
<h2>10. Views de entitlements (com limits)</h2>
|
||||
<p>Para atender o front com 1 query, criamos views “full” e “json”.</p>
|
||||
|
||||
<h3>10.1 v_tenant_entitlements_full</h3>
|
||||
<pre><code>create or replace view public.v_tenant_entitlements_full as
|
||||
select
|
||||
a.tenant_id,
|
||||
f.key as feature_key,
|
||||
(pf.enabled = true) as allowed,
|
||||
pf.limits,
|
||||
a.plan_id,
|
||||
p.key as plan_key
|
||||
from public.v_tenant_active_subscription a
|
||||
join public.plan_features pf on pf.plan_id = a.plan_id
|
||||
join public.features f on f.id = pf.feature_id
|
||||
join public.plans p on p.id = a.plan_id;</code></pre>
|
||||
|
||||
<h3>10.2 v_tenant_entitlements_json</h3>
|
||||
<pre><code>create or replace view public.v_tenant_entitlements_json as
|
||||
select
|
||||
tenant_id,
|
||||
max(plan_key) as plan_key,
|
||||
jsonb_object_agg(
|
||||
feature_key,
|
||||
jsonb_build_object(
|
||||
'allowed', allowed,
|
||||
'limits', coalesce(limits, '{}'::jsonb)
|
||||
)
|
||||
order by feature_key
|
||||
) as entitlements
|
||||
from public.v_tenant_entitlements_full
|
||||
group by tenant_id;</code></pre>
|
||||
|
||||
<div class="callout info">
|
||||
<strong>Uso no front:</strong> uma única consulta retorna <code>plan_key</code> + mapa de entitlements com limits.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="11">
|
||||
<h2>11. Triggers de proteção (Guardrails)</h2>
|
||||
<p>Triggers confirmadas em <code>public.plans</code>:</p>
|
||||
<pre><code>trg_no_delete_core_plans
|
||||
trg_no_change_plan_target
|
||||
trg_no_change_core_plan_key</code></pre>
|
||||
|
||||
<h3>11.1 Funções (versões base)</h3>
|
||||
<pre><code>-- guard_no_delete_core_plans(): impede deletar planos core
|
||||
-- guard_no_change_core_plan_key(): impede alterar key dos planos core
|
||||
-- guard_no_change_plan_target(): impede alterar target de qualquer plano</code></pre>
|
||||
|
||||
<div class="callout warn">
|
||||
<strong>Armadilha comum:</strong> tentar “corrigir plano core” via UPDATE direto. O trigger bloqueia e isso é desejável.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="12">
|
||||
<h2>12. Correção segura de plano core (bypass controlado)</h2>
|
||||
<p>
|
||||
Caso real desta sessão: <code>clinic_free</code> estava com <code>target</code> incorreto.
|
||||
O objetivo foi corrigir sem “desligar guardrails”.
|
||||
</p>
|
||||
|
||||
<h3>12.1 Patch do guardrail para bypass por transação</h3>
|
||||
<pre><code>create or replace function public.guard_no_change_plan_target()
|
||||
returns trigger
|
||||
language plpgsql
|
||||
as $$
|
||||
declare
|
||||
v_bypass text;
|
||||
begin
|
||||
v_bypass := current_setting('app.plan_migration_bypass', true);
|
||||
|
||||
if v_bypass = '1' then
|
||||
return new;
|
||||
end if;
|
||||
|
||||
if new.target is distinct from old.target then
|
||||
raise exception 'Não é permitido alterar target do plano (%) de % para %.',
|
||||
old.key, old.target, new.target
|
||||
using errcode = 'P0001';
|
||||
end if;
|
||||
|
||||
return new;
|
||||
end
|
||||
$$;</code></pre>
|
||||
|
||||
<h3>12.2 Função administrativa (SECURITY DEFINER)</h3>
|
||||
<pre><code>create or replace function public.admin_fix_plan_target(
|
||||
p_plan_key text,
|
||||
p_new_target text
|
||||
) returns void
|
||||
language plpgsql
|
||||
security definer
|
||||
as $$
|
||||
declare
|
||||
v_plan_id uuid;
|
||||
begin
|
||||
if p_new_target not in ('clinic','therapist') then
|
||||
raise exception 'Target inválido: %', p_new_target using errcode='P0001';
|
||||
end if;
|
||||
|
||||
select id into v_plan_id
|
||||
from public.plans
|
||||
where key = p_plan_key
|
||||
for update;
|
||||
|
||||
if v_plan_id is null then
|
||||
raise exception 'Plano não encontrado: %', p_plan_key using errcode='P0001';
|
||||
end if;
|
||||
|
||||
if exists (select 1 from public.subscriptions s where s.plan_id = v_plan_id) then
|
||||
raise exception 'Plano % possui subscriptions. Migração bloqueada.', p_plan_key using errcode='P0001';
|
||||
end if;
|
||||
|
||||
perform set_config('app.plan_migration_bypass', '1', true);
|
||||
|
||||
update public.plans
|
||||
set target = p_new_target
|
||||
where id = v_plan_id;
|
||||
end
|
||||
$$;</code></pre>
|
||||
|
||||
<h3>12.3 Execução (caso real)</h3>
|
||||
<pre><code>select public.admin_fix_plan_target('clinic_free', 'clinic');</code></pre>
|
||||
|
||||
<div class="callout ok">
|
||||
<strong>Resultado:</strong> plano core corrigido, guardrail permanece ativo. Bypass vale apenas na transação.
|
||||
</div>
|
||||
|
||||
<h3>12.4 Hardening recomendado</h3>
|
||||
<pre><code>revoke execute on function public.admin_fix_plan_target(text, text) from public;
|
||||
-- depois conceder apenas ao role administrativo apropriado</code></pre>
|
||||
</section>
|
||||
|
||||
<section class="section" id="13">
|
||||
<h2>13. Seeder idempotente: features + plan_features</h2>
|
||||
<p>
|
||||
O banco já continha features. O mapeamento MVP de plan_features foi validado e segue a ideia:
|
||||
PRO habilita tudo e limites “altos”; FREE habilita subset e limites menores.
|
||||
</p>
|
||||
<pre><code>-- padrão do seed (exemplo):
|
||||
-- insert into features(key, descricao, description) values (...)
|
||||
-- on conflict (key) do update set ...
|
||||
|
||||
-- insert into plan_features(plan_id, feature_id, enabled, limits) values (...)
|
||||
-- on conflict (plan_id, feature_id) do update set enabled=excluded.enabled, limits=excluded.limits;</code></pre>
|
||||
<div class="callout info">
|
||||
<strong>Dica operacional:</strong> manter seed idempotente evita “duplicação” e reduz bugs em ambientes de teste.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="14">
|
||||
<h2>14. Seeder idempotente: subscription de teste</h2>
|
||||
<p>Como as views dependem de uma subscription ativa, criamos uma assinatura manual de teste para um tenant real.</p>
|
||||
<pre><code>insert into public.subscriptions (
|
||||
tenant_id,
|
||||
plan_id,
|
||||
status,
|
||||
plan_key,
|
||||
"interval",
|
||||
current_period_start,
|
||||
current_period_end,
|
||||
provider,
|
||||
source
|
||||
)
|
||||
values (
|
||||
'<TENANT_UUID>',
|
||||
(select id from public.plans where key = 'clinic_free'),
|
||||
'active',
|
||||
'clinic_free',
|
||||
'month',
|
||||
now(),
|
||||
null,
|
||||
'manual',
|
||||
'manual'
|
||||
);</code></pre>
|
||||
|
||||
<div class="callout ok">
|
||||
<strong>Validação:</strong> após inserir, <code>v_tenant_active_subscription</code> e <code>v_tenant_entitlements_json</code> devem retornar dados.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="15">
|
||||
<h2>15. Front-end: padrões e telas</h2>
|
||||
<p><strong>Padrões adotados nesta sessão:</strong></p>
|
||||
<ul>
|
||||
<li>Em arquivos Vue: <strong>script</strong> → <strong>template</strong> → <strong>style</strong>.</li>
|
||||
<li>Busca com <code>FloatLabel</code> + <code>IconField</code> + <code>InputIcon</code>.</li>
|
||||
<li>Telas operacionais: DataTable com paginação, estados empty e UX “foco” via <code>?q=...</code>.</li>
|
||||
</ul>
|
||||
|
||||
<div class="callout info">
|
||||
<strong>Melhorias aplicadas:</strong> Cards para “foco”, botão voltar no topo, textos mais claros e layout mais estável.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="16">
|
||||
<h2>16. Troubleshooting (erros reais)</h2>
|
||||
|
||||
<h3>16.1 “Não é permitido alterar target do plano …”</h3>
|
||||
<p>Causa: trigger <code>guard_no_change_plan_target</code>. Solução: bypass controlado + função admin (seção 12).</p>
|
||||
|
||||
<h3>16.2 “Não é permitido alterar a key do plano padrão …”</h3>
|
||||
<p>Causa: trigger <code>guard_no_change_core_plan_key</code>. Solução: não renomear core; criar novo plano se necessário.</p>
|
||||
|
||||
<h3>16.3 Entitlements view vazia</h3>
|
||||
<p>Causa: ausência de subscription ativa em <code>v_tenant_active_subscription</code>. Solução: inserir subscription de teste (seção 14).</p>
|
||||
|
||||
<div class="callout warn">
|
||||
<strong>Lembrete:</strong> quando algo “não retorna nada”, primeiro verifique as views-base antes de mexer no front.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="17">
|
||||
<h2>17. Checklist de validação</h2>
|
||||
<ul>
|
||||
<li><strong>Plans:</strong> core existe e está ativo; targets corretos.</li>
|
||||
<li><strong>Pricing:</strong> PRO tem preço vigente (active_to null); FREE pode ficar sem price.</li>
|
||||
<li><strong>Subscriptions:</strong> existe ao menos 1 assinatura ativa para testar.</li>
|
||||
<li><strong>Entitlements:</strong> <code>v_tenant_entitlements_json</code> retorna mapa com <code>allowed</code> + <code>limits</code>.</li>
|
||||
<li><strong>Guardrails:</strong> triggers ativas; correção de core somente via função admin.</li>
|
||||
<li><strong>Front:</strong> telas operacionais OK; foco via query; layout consistente.</li>
|
||||
</ul>
|
||||
<div class="callout ok">
|
||||
<strong>Meta:</strong> com este checklist, qualquer dev/operador consegue diagnosticar Billing em minutos.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
<footer>
|
||||
<div><strong>Agência PSI — Documento Mestre Billing v2.0</strong></div>
|
||||
<div class="small">Gerado em 2026-03-01 10:43:18 UTC. Estrutura inspirada no padrão interno com sidebar + anchors.</div>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
206
docs/billing/Agencia_PSI_Billing_Subscriptions_v1_2.html
Normal file
206
docs/billing/Agencia_PSI_Billing_Subscriptions_v1_2.html
Normal file
@@ -0,0 +1,206 @@
|
||||
<!doctype html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Agência PSI — Billing & Subscriptions v1.2</title>
|
||||
<style>
|
||||
body{
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial;
|
||||
margin:0;
|
||||
padding:40px;
|
||||
background:#f6f8fc;
|
||||
color:#0f172a;
|
||||
}
|
||||
h1{font-size:28px;margin-bottom:8px;}
|
||||
h2{margin-top:40px;font-size:20px;}
|
||||
h3{margin-top:24px;font-size:16px;}
|
||||
p{line-height:1.6;color:#334155;}
|
||||
pre{
|
||||
background:#0f172a;
|
||||
color:#e2e8f0;
|
||||
padding:16px;
|
||||
border-radius:12px;
|
||||
overflow:auto;
|
||||
}
|
||||
code{font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;}
|
||||
.section{margin-bottom:40px;}
|
||||
.badge{
|
||||
display:inline-block;
|
||||
padding:4px 10px;
|
||||
border-radius:999px;
|
||||
background:#e2e8f0;
|
||||
font-size:12px;
|
||||
margin-right:6px;
|
||||
}
|
||||
.rule{
|
||||
background:#e0f2fe;
|
||||
padding:14px;
|
||||
border-left:4px solid #0284c7;
|
||||
border-radius:10px;
|
||||
margin-top:12px;
|
||||
}
|
||||
.warn{
|
||||
background:#fef3c7;
|
||||
padding:14px;
|
||||
border-left:4px solid #d97706;
|
||||
border-radius:10px;
|
||||
margin-top:12px;
|
||||
}
|
||||
.danger{
|
||||
background:#fee2e2;
|
||||
padding:14px;
|
||||
border-left:4px solid #dc2626;
|
||||
border-radius:10px;
|
||||
margin-top:12px;
|
||||
}
|
||||
.ok{
|
||||
background:#dcfce7;
|
||||
padding:14px;
|
||||
border-left:4px solid #16a34a;
|
||||
border-radius:10px;
|
||||
margin-top:12px;
|
||||
}
|
||||
footer{
|
||||
margin-top:60px;
|
||||
font-size:12px;
|
||||
color:#64748b;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Billing & Subscriptions — v1.2</h1>
|
||||
<p><strong>Agência PSI</strong> — Documento consolidado da sessão técnica sobre Subscriptions, Guardrails e Seeder.</p>
|
||||
|
||||
<div class="section">
|
||||
<h2>1. Escopo desta versão</h2>
|
||||
<p>Este documento consolida tudo o que foi tratado nesta sessão:</p>
|
||||
<ul>
|
||||
<li>Modelagem real da tabela <code>subscriptions</code></li>
|
||||
<li>Histórico via <code>subscription_events</code></li>
|
||||
<li>Triggers (guardrails) de proteção</li>
|
||||
<li>Views oficiais</li>
|
||||
<li>Seeder completo (planos + preços + metadata pública)</li>
|
||||
<li>Erros reais encontrados e solução aplicada</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>2. Estrutura confirmada — subscriptions</h2>
|
||||
<pre><code>id uuid PK
|
||||
tenant_id uuid NULL
|
||||
user_id uuid NULL
|
||||
plan_id uuid NOT NULL
|
||||
plan_key text NULL
|
||||
interval text CHECK ('month','year')
|
||||
status text DEFAULT 'active'
|
||||
current_period_start timestamptz
|
||||
current_period_end timestamptz
|
||||
provider text DEFAULT 'manual'
|
||||
cancel_at_period_end boolean DEFAULT false
|
||||
created_at timestamptz DEFAULT now()
|
||||
updated_at timestamptz DEFAULT now()</code></pre>
|
||||
|
||||
<div class="rule">
|
||||
Assinatura de clínica exige <strong>tenant_id</strong>.
|
||||
Assinatura de terapeuta pode usar <strong>user_id</strong>.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>3. Guardrails (Proteções de Integridade)</h2>
|
||||
|
||||
<h3>3.1 Impedir deletar planos core</h3>
|
||||
<pre><code>create or replace function guard_no_delete_core_plans()
|
||||
returns trigger language plpgsql as $$
|
||||
begin
|
||||
if old.key in ('clinic_free','clinic_pro','therapist_free','therapist_pro') then
|
||||
raise exception 'Plano padrão (%) não pode ser removido.', old.key;
|
||||
end if;
|
||||
return old;
|
||||
end $$;</code></pre>
|
||||
|
||||
<h3>3.2 Impedir alterar target</h3>
|
||||
<pre><code>create or replace function guard_no_change_plan_target()
|
||||
returns trigger language plpgsql as $$
|
||||
begin
|
||||
if new.target is distinct from old.target then
|
||||
raise exception 'Não é permitido alterar target do plano.';
|
||||
end if;
|
||||
return new;
|
||||
end $$;</code></pre>
|
||||
|
||||
<h3>3.3 Impedir alterar key core</h3>
|
||||
<pre><code>create or replace function guard_no_change_core_plan_key()
|
||||
returns trigger language plpgsql as $$
|
||||
begin
|
||||
if old.key in ('clinic_free','clinic_pro','therapist_free','therapist_pro')
|
||||
and new.key is distinct from old.key then
|
||||
raise exception 'Não é permitido alterar a key do plano padrão.';
|
||||
end if;
|
||||
return new;
|
||||
end $$;</code></pre>
|
||||
|
||||
<div class="warn">
|
||||
Esses guardrails impediram alterações indevidas quando tentamos renomear planos core.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>4. Views Oficiais</h2>
|
||||
<p><strong>v_public_pricing</strong> — Tela pública de preços.</p>
|
||||
<p><strong>v_tenant_active_subscription</strong> — Plano vigente do tenant.</p>
|
||||
<p><strong>v_subscription_health</strong> — Diagnóstico de inconsistências.</p>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>5. Seeder Oficial (MVP)</h2>
|
||||
|
||||
<pre><code>insert into plans (key,name,target,is_active)
|
||||
values
|
||||
('clinic_free','Clinic Free','clinic',true),
|
||||
('clinic_pro','Clinic Pro','clinic',true),
|
||||
('therapist_free','Therapist Free','therapist',true),
|
||||
('therapist_pro','Therapist Pro','therapist',true)
|
||||
on conflict (key) do update set
|
||||
name=excluded.name,
|
||||
target=excluded.target,
|
||||
is_active=excluded.is_active;</code></pre>
|
||||
|
||||
<div class="ok">
|
||||
Seeder é idempotente. Pode rodar múltiplas vezes sem duplicar.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>6. Incidentes reais resolvidos</h2>
|
||||
|
||||
<h3>6.1 Pricing retornando null</h3>
|
||||
<p>Causa: não havia preço vigente (is_active=true e active_to is null).</p>
|
||||
|
||||
<h3>6.2 Erro ao alterar plano padrão</h3>
|
||||
<p>Causa: trigger guard_no_change_core_plan_key bloqueando alteração.</p>
|
||||
|
||||
<h3>6.3 Assinatura sem tenant_id</h3>
|
||||
<p>Causa: regra de negócio no banco impedindo clinic sem tenant.</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>7. Diretrizes finais</h2>
|
||||
<ul>
|
||||
<li>Plano nunca deve ser inferido do role.</li>
|
||||
<li>UI deve consumir apenas views oficiais.</li>
|
||||
<li>Plano core nunca deve ser renomeado.</li>
|
||||
<li>Preço sempre deve ter vigência ativa.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
Agência PSI — Billing & Subscriptions v1.2<br>
|
||||
Documento gerado automaticamente.
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
308
docs/billing/documentacao-billing-completa-agencia-psi.html
Normal file
308
docs/billing/documentacao-billing-completa-agencia-psi.html
Normal file
@@ -0,0 +1,308 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Arquitetura Técnica Completa — Billing & Assinaturas | Agência PSI</title>
|
||||
<style>
|
||||
body{font-family:Arial,Helvetica,sans-serif;margin:40px;line-height:1.65;color:#111}
|
||||
h1,h2,h3{margin-top:36px}
|
||||
code,pre{background:#f4f4f4;padding:12px;border-radius:8px;display:block;overflow:auto;font-size:13px}
|
||||
.section{margin-bottom:40px}
|
||||
.small{font-size:13px;color:#555}
|
||||
ul{margin-left:20px}
|
||||
.diagram{background:#fafafa;border:1px solid #ddd;padding:16px;border-radius:8px;font-family:monospace;font-size:13px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Arquitetura Técnica Completa — Billing & Assinaturas</h1>
|
||||
<p class="small">Projeto: Agência PSI • Documento estrutural definitivo do domínio de Billing.</p>
|
||||
|
||||
<div class="section">
|
||||
<h2>1. Visão Arquitetural Geral</h2>
|
||||
|
||||
<div class="diagram">
|
||||
USUÁRIO
|
||||
│
|
||||
▼
|
||||
subscription_intents (VIEW unificada)
|
||||
│
|
||||
▼ (RPC activate_subscription_from_intent)
|
||||
subscriptions
|
||||
│
|
||||
▼
|
||||
subscription_events (auditoria)
|
||||
│
|
||||
▼
|
||||
entitlements (derivados do plano)
|
||||
</div>
|
||||
|
||||
<p>Separação estrutural:</p>
|
||||
<ul>
|
||||
<li><strong>plans</strong> → catálogo</li>
|
||||
<li><strong>plan_prices</strong> → preço versionado</li>
|
||||
<li><strong>subscription_intents_*</strong> → intenção pré-pagamento</li>
|
||||
<li><strong>subscriptions</strong> → assinatura ativa</li>
|
||||
<li><strong>subscription_events</strong> → histórico</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>2. Estados Oficiais da Assinatura</h2>
|
||||
<pre>
|
||||
pending
|
||||
active
|
||||
past_due
|
||||
suspended
|
||||
cancelled
|
||||
expired
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>3. Índice de Integridade de Preços</h2>
|
||||
<pre>
|
||||
create unique index if not exists uq_plan_price_active
|
||||
on plan_prices (plan_id, interval, currency)
|
||||
where is_active = true and active_to is null;
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>4. Função Completa — activate_subscription_from_intent</h2>
|
||||
<pre>
|
||||
CREATE OR REPLACE FUNCTION public.activate_subscription_from_intent(p_intent_id uuid)
|
||||
RETURNS subscriptions
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
AS $function$
|
||||
declare
|
||||
v_intent record;
|
||||
v_sub public.subscriptions;
|
||||
v_days int;
|
||||
v_user_id uuid;
|
||||
v_plan_id uuid;
|
||||
v_target text;
|
||||
begin
|
||||
|
||||
select * into v_intent
|
||||
from public.subscription_intents
|
||||
where id = p_intent_id;
|
||||
|
||||
if not found then
|
||||
raise exception 'Intent não encontrada';
|
||||
end if;
|
||||
|
||||
if v_intent.status <> 'paid' then
|
||||
raise exception 'Intent precisa estar paid';
|
||||
end if;
|
||||
|
||||
select p.id, p.target
|
||||
into v_plan_id, v_target
|
||||
from public.plans p
|
||||
where p.key = v_intent.plan_key
|
||||
limit 1;
|
||||
|
||||
if v_plan_id is null then
|
||||
raise exception 'Plano não encontrado';
|
||||
end if;
|
||||
|
||||
v_target := lower(coalesce(v_target,''));
|
||||
|
||||
if v_target = 'clinic' and v_intent.tenant_id is null then
|
||||
raise exception 'Intent clinic exige tenant_id';
|
||||
end if;
|
||||
|
||||
if v_target = 'therapist' and v_intent.tenant_id is not null then
|
||||
raise exception 'Intent therapist não deve ter tenant_id';
|
||||
end if;
|
||||
|
||||
v_days := case when v_intent.interval = 'year' then 365 else 30 end;
|
||||
|
||||
v_user_id := coalesce(v_intent.created_by_user_id, v_intent.user_id);
|
||||
|
||||
if v_user_id is null then
|
||||
raise exception 'user_id obrigatório';
|
||||
end if;
|
||||
|
||||
if v_target = 'clinic' then
|
||||
update subscriptions
|
||||
set status = 'cancelled',
|
||||
cancelled_at = now()
|
||||
where tenant_id = v_intent.tenant_id
|
||||
and status = 'active';
|
||||
else
|
||||
update subscriptions
|
||||
set status = 'cancelled',
|
||||
cancelled_at = now()
|
||||
where user_id = v_user_id
|
||||
and tenant_id is null
|
||||
and status = 'active';
|
||||
end if;
|
||||
|
||||
insert into subscriptions (
|
||||
user_id,
|
||||
plan_id,
|
||||
status,
|
||||
current_period_start,
|
||||
current_period_end,
|
||||
tenant_id,
|
||||
plan_key,
|
||||
interval,
|
||||
provider,
|
||||
started_at,
|
||||
activated_at
|
||||
)
|
||||
values (
|
||||
v_user_id,
|
||||
v_plan_id,
|
||||
'active',
|
||||
now(),
|
||||
now() + make_interval(days => v_days),
|
||||
case when v_target='clinic' then v_intent.tenant_id else null end,
|
||||
v_intent.plan_key,
|
||||
v_intent.interval,
|
||||
'manual',
|
||||
now(),
|
||||
now()
|
||||
)
|
||||
returning * into v_sub;
|
||||
|
||||
return v_sub;
|
||||
|
||||
end;
|
||||
$function$;
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>5. Função Completa — transition_subscription (segura)</h2>
|
||||
<pre>
|
||||
create or replace function public.transition_subscription(
|
||||
p_subscription_id uuid,
|
||||
p_to_status text,
|
||||
p_reason text default null,
|
||||
p_metadata jsonb default null
|
||||
)
|
||||
returns subscriptions
|
||||
language plpgsql
|
||||
security definer
|
||||
as $$
|
||||
declare
|
||||
v_sub subscriptions;
|
||||
v_uid uuid;
|
||||
v_allowed boolean := false;
|
||||
begin
|
||||
|
||||
v_uid := auth.uid();
|
||||
|
||||
select * into v_sub
|
||||
from subscriptions
|
||||
where id = p_subscription_id;
|
||||
|
||||
if not found then
|
||||
raise exception 'Assinatura não encontrada';
|
||||
end if;
|
||||
|
||||
if is_saas_admin() then
|
||||
v_allowed := true;
|
||||
end if;
|
||||
|
||||
if not v_allowed
|
||||
and v_sub.tenant_id is null
|
||||
and v_sub.user_id = v_uid then
|
||||
v_allowed := true;
|
||||
end if;
|
||||
|
||||
if not v_allowed
|
||||
and v_sub.tenant_id is not null then
|
||||
|
||||
if exists (
|
||||
select 1 from tenant_members tm
|
||||
where tm.tenant_id = v_sub.tenant_id
|
||||
and tm.user_id = v_uid
|
||||
and tm.status = 'active'
|
||||
and tm.role = 'tenant_admin'
|
||||
) then
|
||||
v_allowed := true;
|
||||
end if;
|
||||
|
||||
end if;
|
||||
|
||||
if not v_allowed then
|
||||
raise exception 'Sem permissão';
|
||||
end if;
|
||||
|
||||
update subscriptions
|
||||
set status = p_to_status,
|
||||
updated_at = now()
|
||||
where id = p_subscription_id
|
||||
returning * into v_sub;
|
||||
|
||||
insert into subscription_events (
|
||||
subscription_id,
|
||||
owner_id,
|
||||
event_type,
|
||||
created_at,
|
||||
created_by,
|
||||
source,
|
||||
reason,
|
||||
metadata
|
||||
)
|
||||
values (
|
||||
v_sub.id,
|
||||
coalesce(v_sub.tenant_id, v_sub.user_id),
|
||||
'status_changed',
|
||||
now(),
|
||||
v_uid,
|
||||
'manual_transition',
|
||||
p_reason,
|
||||
p_metadata
|
||||
);
|
||||
|
||||
return v_sub;
|
||||
|
||||
end;
|
||||
$$;
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>6. Máquina de Estados Recomendada</h2>
|
||||
|
||||
<div class="diagram">
|
||||
pending → active → past_due → suspended → cancelled
|
||||
↓
|
||||
expired
|
||||
</div>
|
||||
|
||||
<p>Recomendação futura: validar allowed_transitions em tabela dedicada.</p>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>7. Checklist de Validação Estrutural</h2>
|
||||
<ul>
|
||||
<li>Intent paga gera subscription ativa</li>
|
||||
<li>subscription_id vinculado corretamente</li>
|
||||
<li>Cancelamento gera evento</li>
|
||||
<li>Reativação preserva histórico</li>
|
||||
<li>Tenant isolation validado</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>8. Roadmap Estrutural Futuro</h2>
|
||||
<ul>
|
||||
<li>State machine formal com allowed_transitions</li>
|
||||
<li>Automação de expiração por cron</li>
|
||||
<li>Integração Stripe mantendo arquitetura</li>
|
||||
<li>Health monitor automatizado</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
<p class="small">Documento técnico estrutural consolidado após implementação real validada.</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
15
docs/comandos.txt
Normal file
15
docs/comandos.txt
Normal file
@@ -0,0 +1,15 @@
|
||||
para gerar o sql
|
||||
supabase db dump --local -f full_dump.sql
|
||||
|
||||
para exportar todo o banco:
|
||||
docker exec -i supabase_db_agenciapsi-primesakai pg_dump -U postgres postgres > backup.sql
|
||||
|
||||
para exportar o schema.sql:
|
||||
docker exec -i supabase_db_agenciapsi-primesakai pg_dump -U postgres --schema-only postgres > schema.sql
|
||||
|
||||
para restaurar o banco:
|
||||
docker exec -i supabase_db_agenciapsi-primesakai psql -U postgres postgres < backup.sql
|
||||
|
||||
para exportar sem ownership e ACLs (deixa o dump portável para outro ambiente):
|
||||
docker exec -i supabase_db_agenciapsi-primesakai pg_dump -U postgres --no-owner --no-acl postgres > full_dump.sql
|
||||
|
||||
51
docs/dados-padrões-da-agenda.txt
Normal file
51
docs/dados-padrões-da-agenda.txt
Normal file
@@ -0,0 +1,51 @@
|
||||
📌 Padrões da Agenda Clínica
|
||||
|
||||
Base 24h por padrão
|
||||
|
||||
Função principal: adicionar pacientes manualmente
|
||||
|
||||
Exibir automaticamente agendamentos vindos da Agenda Pública
|
||||
|
||||
Permitir adicionar tarefas pessoais
|
||||
|
||||
Permitir adicionar bloqueios
|
||||
|
||||
Unificar tudo em uma única linha do tempo
|
||||
|
||||
📐 Estrutura e Funcionamento
|
||||
|
||||
Estrutura padrão: Agenda aberta (24h visível)
|
||||
|
||||
Jornada de trabalho configurável por dia
|
||||
|
||||
Pausas são estruturais (não viram compromissos)
|
||||
|
||||
Disponibilidade padrão: atender em toda a jornada
|
||||
|
||||
⏱ Ciclo de Atendimento (Padrão)
|
||||
|
||||
Duração padrão: 50 minutos
|
||||
|
||||
Intervalo padrão: 10 minutos
|
||||
|
||||
Início padrão: hora cheia (:00)
|
||||
|
||||
⚠️ Regras Importantes
|
||||
|
||||
Nunca bloquear atendimento fora da jornada
|
||||
|
||||
Sempre avisar quando ultrapassar
|
||||
|
||||
Classificar internamente níveis de extrapolação
|
||||
|
||||
👁 Preview do Wizard
|
||||
|
||||
Mostrar apenas um dia
|
||||
|
||||
Sempre o primeiro dia ativo da jornada
|
||||
|
||||
Simulação completa e visual
|
||||
|
||||
Não operacional
|
||||
|
||||
Agora sim você tem o “Estado Default Oficial da Agenda Clínica”.
|
||||
1798
docs/estrategia/plataforma_saude_mental.html
Normal file
1798
docs/estrategia/plataforma_saude_mental.html
Normal file
File diff suppressed because it is too large
Load Diff
429
docs/estrategia/plataforma_saude_mental_estrategia.pdf
Normal file
429
docs/estrategia/plataforma_saude_mental_estrategia.pdf
Normal file
@@ -0,0 +1,429 @@
|
||||
%PDF-1.4
|
||||
%“Œ‹ž ReportLab Generated PDF document (opensource)
|
||||
1 0 obj
|
||||
<<
|
||||
/F1 2 0 R /F2 3 0 R /F3 6 0 R /F4 23 0 R
|
||||
>>
|
||||
endobj
|
||||
2 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
3 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
4 0 obj
|
||||
<<
|
||||
/Contents 28 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 27 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
5 0 obj
|
||||
<<
|
||||
/Contents 29 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 27 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
6 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica-BoldOblique /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
7 0 obj
|
||||
<<
|
||||
/Contents 30 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 27 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
8 0 obj
|
||||
<<
|
||||
/Contents 31 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 27 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
9 0 obj
|
||||
<<
|
||||
/Contents 32 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 27 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
10 0 obj
|
||||
<<
|
||||
/Contents 33 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 27 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
11 0 obj
|
||||
<<
|
||||
/Contents 34 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 27 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
12 0 obj
|
||||
<<
|
||||
/Contents 35 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 27 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
13 0 obj
|
||||
<<
|
||||
/Contents 36 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 27 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
14 0 obj
|
||||
<<
|
||||
/Contents 37 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 27 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
15 0 obj
|
||||
<<
|
||||
/Contents 38 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 27 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
16 0 obj
|
||||
<<
|
||||
/Contents 39 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 27 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
17 0 obj
|
||||
<<
|
||||
/Contents 40 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 27 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
18 0 obj
|
||||
<<
|
||||
/Contents 41 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 27 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
19 0 obj
|
||||
<<
|
||||
/Contents 42 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 27 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
20 0 obj
|
||||
<<
|
||||
/Contents 43 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 27 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
21 0 obj
|
||||
<<
|
||||
/Contents 44 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 27 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
22 0 obj
|
||||
<<
|
||||
/Contents 45 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 27 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
23 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica-Oblique /Encoding /WinAnsiEncoding /Name /F4 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
24 0 obj
|
||||
<<
|
||||
/Contents 46 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 27 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
25 0 obj
|
||||
<<
|
||||
/PageMode /UseNone /Pages 27 0 R /Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
26 0 obj
|
||||
<<
|
||||
/Author (\(anonymous\)) /CreationDate (D:20260304134538+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260304134538+00'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
27 0 obj
|
||||
<<
|
||||
/Count 19 /Kids [ 4 0 R 5 0 R 7 0 R 8 0 R 9 0 R 10 0 R 11 0 R 12 0 R 13 0 R 14 0 R
|
||||
15 0 R 16 0 R 17 0 R 18 0 R 19 0 R 20 0 R 21 0 R 22 0 R 24 0 R ] /Type /Pages
|
||||
>>
|
||||
endobj
|
||||
28 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1167
|
||||
>>
|
||||
stream
|
||||
Gatn%9lJcG&;KZQ'm&.^C+a/oIIqEJ!_'EL2JQ$u#dP)QLo&UeZ2=-:8:<OZ>Qh/4-^6S7+247jB`uNoVSC([i6Mi2\3`H4llJt2%G<V?Zc2Zm5`*<$#kV!u!^RtnJGmB[ig2eX@C/p?Je@(&"nFuoX#Zo-q_nm(PV8%l;\8^`6P-\qJ=g;k@]Zeb!eMdGdKnM9If"B'b#VUDI(ib"]/ABHmZ\o5goUP$<nG)Rp'n;dh*>r`o>@We7<$tO%f`[aNk;T3C02RHIGWa.K+H_2L-$RaJ(<*JG#MG\2Lh7@-%`DY.M0F2-S>@Bgq[503JsN;OD(0K`*fPU*PZ!WQ7SmR`9d8i0PL\,j?XF((iDb@YN>b&VmNH)Kl!GSP%e--%Q/('VVG61"7Z/q9g)cO^ST)8d/eR)EO.u(#:]R2%XBA%;Z7@X9oE`Za[Da\O8AT%Fj%#G#]#[`r0n"s@fY\c,;b]0]gChTp*tMP=<-llD/7:**T9RZFEBusU;6`\k#kIPSBKBSK`*YBL'cHg^IJXg;@od+;7`Tc:3\@;MO@^USq^P/O@l2.m$SO6pbC]>"XF_:^.4ftR]Br+abt"ANV0S>I<U-'L0LpET!&B/ju_/3(bXSrJ@i?iroKd`Mr'VMl,qCnF`JOPIc$FNNjBJ\H6^<Pgs-O/Zj8];(^&h9n?\!E''+gY=;UYhYWXi)/r-1X4[#)^mSgjGiIMBGXmk>4FZ.dYaPTaUANJ&J\D#E8+qO$;??tFjQ^_KI1SF%Vl`/W=a*a(<)/m[tg?7fhq%NAKZ/Gipr250Qi$ZX,)U@+^"@5Rp[#Tn?mlrR@mJ/TC_AfI_aZ$=^b;A9n`@-rId;Td0:pDOqF!;BSW"u:m$2o0(SYLh$cJ/B;aHJH#rq&esag8gH%Xk@]d1>ds-b/fuN-ZmHX3N$J^cH0%C>R\lDiTTofHi;FF<0[?#Nud`qjX(--HSJ:aNu5H5#shEc)[O>eI1q#;37&@@`KJ7cV\'b[r[R0a`n(PIo0T3WaK2+&6J\$"P1'rmMRcm)(0>1I.R"UPe1%6CrXMh'ZW':)7VTM4lQQn^TeZ$XV`U,MDT,V5mGYfZ[F0Ym4)^,edUG)([]IiGV2c5b;!lIn^9+>&]`bB4`hnJDG0d:H\\)aS(L$T(*oC<Dn>@4l%6nS-^SF[~>endstream
|
||||
endobj
|
||||
29 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1306
|
||||
>>
|
||||
stream
|
||||
Gb!$H95iQE&BF8='QZqUPfrg]kfjJem)F2'S:&`,[%T`#2j0[7qWW4"8:DgYp9^]),c4:n1S"9'r0(_Xn\s5^kn`4'"oNAXJ8n\mJOq%o0nJZ0k\3F%APP$(bYmCZ&FPu?T\PJ5I+U2?PS=1`;hQ*dJQj=oQIs'lqXXtS`>>t#9lR=r"\3ZI!5E*901loF\78pW+DhXI/R6LHj2LfUDE])YeMZ-CQU5XB9Wu2a`fj3&G_F'!Y+oS3mrpFT--f&&gAP#BG?\G(!NrK79^8HNCm1[rQCJo(%)k83q'+(Hr5]Xn8m<lt3%YTiphm=OqO#Vs0SC^9.AOI^@&Onupjd6kM8@^=@!;MgU=p3,&0AiJ;$m\jrsuiFQ9pH/N6iB=3T]P&oWnmd't#]QJ.CN4Z8rs9UJIjC>Y,16GDB_>('dIu>BdRl6jmF!h0F"5[=2ZM#BJ<YVg130UF`Yga\'#a@s2CI6CYC&2,5+JGamoTqk)?6TPXW#m_nDZm7L%8_6/KAYg'XSDignMVZp(YK:1V?3Sf;:N`bTuI`ULIh>VBq(li%Crf8!8g(%1nJaXO?NG)OE!._K;C*mCK!W2%3kWVrfV<ngDfnZTnZCTI#^,4D3XC&'eK!IRJ?VYCn`)#rajLlAZ=XrD'q`A,g'/(sZZB!XC?R@e7!mJgVkYL\qAY1tL\^5^#+&8[4Y`D=Jj(oe@Yn+]c`K:hknF$s'5rKF3gcDf[ET/o;:^n[<+@piW<4==u8GOhEg?@Lg^uM),=(WDd"V9'YB?pln@9PL^O<C<\T]g>c&<TLs(4j5@^J3X#k@*eZO?`VZW1orXU7oBaE);Y$:mh)79lOJ/;#+DI.'YUn,q:bo#6u7W#Nb;kE):OnQmCq!81Ia;4VqI&QIjeN_[f<Y,"sg;b^]hlN^LapI+i/>YBOR+WQ3OAP'XL_3045?O!`FIEM$6R,8mAj]["78P.uDd"ToPM"$RjlB]EKNa+#[A]0'';C="2VgVp:ul8'J/%(=1"M_6!.m_a$Jo=mX9h1.nhMZ^pL:[2I2aqk\f=^$R8-Y8DTRlE7oBJer3BZQr/ES_ucXF)'-'#+6;ZWs;/BQ"mu?Y@.!IIHYD%2fTa3?0cq,2G^0?i@HL`D'8+p9\i$.pkDor&JPB,`'?mMT,i>Nkf1fXsYmG:kVQ2Ctj$NoO.sI$NXJmU0<#'_h3fbK5e_*ROll5jZKSA<1/`;W3]4-D.n'p>H-4*fOui"i<W*ml'Cnb^+irJ]oG?G/k]QJ0m=*DN=IZc_&;LcErR(+Jh[SNVR`NqI`3I]TB@=(UON<:>X4/~>endstream
|
||||
endobj
|
||||
30 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2486
|
||||
>>
|
||||
stream
|
||||
Gau0D?#SK-&q/*0(bDmVDNXMTqkJ)?9j4np2qc.(IE;jAdgcaWF=>7:gg>QW:`l!oVFb`N@3h]qMOZ\J3+p5pH2.1&Io-gVB0lRsi0,80@RY_k,$J6uq;UVs8GJdK-U%i$o>3U4&`e=lm/![#bF'H03!r$1o2l-0S5><(0M3P5:pbOiKLPFICpuhu)=NZFACpm+rP=dZ&P4&)5%R#.g^8ssNit'1(T/?-YJ6UUff@lYhM,Hh_3acN(8huKU;]&p]GVJo&"[)kbVPBZ;7aO?R45^>eZGP:VR<_C%:ami$U_BhOOAJSYOk`CKpLh:09O:HQ@pOS,YG`Qh^Jd/Ll&SC,E.<=a([Yd!N:B1`<uu^#r%PLbea9X`6O"-^uGYDNFNCLriaI&[p]shnqG[7*I'm*O8`f$l+0m'2D>.1l.4)7(df4=5oeuC>.4l?mc`Scm'/Q:(5a@GZ[HoM[\;H([aFR%dJ.#\$uVk\-F*cu.]T,iCpp<5/o#3+i3@-M$hU^e$i9_aju2kK9DWm^I<n1lQZR@uq>#DX\S'!E9o1Xam7bXc46CoS)Jp&U&EX[(LO[ntLG2dD-+V+&4MsTZ2c$QJAGL'?4b"Z2gKRHuH0"ejju`AfqrEd4o+B@1R28$_[VIo.Bb`3I%PLg\=LH+"d*:38;IQS"WEMlN.U@7P2N8tL)f*/:pB#K)jWJ#3q]1a$9%h@9!R/'9[pTIG?a.Xk&9d1j[%=U.A3'E)o=-/7=-4t+dqDk*9Bik(qi6hT/!ku33X'!`lF!?D(tqp!-uf[U=r6a_e?UU/J!:G(9\mPr\R^b?U&O?G__S:483:mQiT!O?F&P`f@.m&*)UfD?'q=?ojeFH/482!&c]eRscC:$kM9,#g3R2s%Nf`oG(1-/#a<GHd,tDM!+X4=1$MLPFM24%,i<K(:7O7X6TNF>1C*9a&JK143h5i>3Q&?C5=kW":BkpI7)Vf0ZZNl[I@R&/$baF#9D>L30;Jd`RH0AdqXd70d.QSUh1+Qp9TeXEI,HQKGlYUJQE@,hXW>Kqh4],TO7S&\>S>U7C\Q8B$3Z\Z4n,dMI2%k#;,NMmD5mt+5<$/C!#NEZVUL;Y3)o]qR\U_Ka1W49k(_p8jY8>,Ac>sSLjW+<rK[b(R/>>*rV[3l(^YYrtY,R\1,nmt*6#F<gAWM'I:X1$173(ess5f.qb;(,k7@jd4T1;aJKj1:IHi!=CeOR@U]1:V3gh0oE4N0UH6mM7R$N4Y&.4dOIq%ad[7!W\/SKJB#%An_bF;YJ^hWp=`'[N>&E*Uq)Diobc'&Q.=Q[qLp"gTlm`t>W-ko*f:)OXV&?!9aV.:!4FmROV.4SA&rS[n?\P='IoK-r5`X8(c]*;PQ.Da.0Y#_MWIan;/BeH%P!PaOLaU1/48cJ8WLRjL6[,QBui>XY).f5m+%V+UrcTatGR3<@%\:JZYkDZAB:IPu/?B>i!#n((:`8'9pC2Hd@oaI/($3tSN(\kuKYc3c1,E,+Xd*+tInO&os*N(0p240HO?&:QOI>;s4]<["joWSkV2YN$@m0r`\2&kWi`s"l"Q896injiYC(@_2(HRJ/88E27GC@n?T(<8^$lVUGKu!;/Ze'd'!agtSHe_J3d4>au9h(%,UXrutRXVdW0r(0&R\9(rLN<D3!%L#4u6'pUTQTTVR>+fgh+OdUlL.nd$"/6lffOfao\dH1VXL8joCF5Zu#MQ3?*&"RV1c\R*Tb8"Y_LJt#WCJ#miXm<A.?jRr49!imr^CBkg<n[1#]1snB.g<dLN6GB:]EB<u$=WXM=pr#WI/\US&qQ_!lVAVK^CY_8bJgB_BK3]T2&=LW)W\ugP9GF&)\Ja4N+[URW:oQsN5O@*"?Z]%2q=M&)e_:EefF<$RCbrAm>NbmD,jrkJ_6?N8cuI<C(4Z)=+BZK%`r3Hh:(:MGURB_i`DGJX8>.)ajjqfngmBMcJ7gg+1>eHAbrr.]c4#>9K_'lC5na$5qU2Y?b9\.*m5iJ+RPVF]g['/.p^?0E!"A:G*3@s]_,KFqiZ#Y](u$hpi.tFMo:PEXN0?8hUG1uBnuH[bHIP`hYK7S!r9d'Ni#PYWH]1%Y.RtKqpli3[&=:Rh\V_"?n/1eJAtfj6YUs8qdkoZr6u6^#C;?R@0+Mg_l;M8%89WKD+_,;4ns9R**STO%_DV[pJlJdkfR$,2$KB\aJ*1K/oVX\FgEgqUnP1VYTnu\/i@%W:mgitm%S"paSHs_7VCDIP>tgccGWF;DR'O0fb/D/)H1Mg2[HbhU8O2]!tQr):`\t]L:u2_CtX;bec7kZ(j$:n46KE<&0R'N:^k,4B7Np\$s5[=n4ofPiWr5#Z(9!:?NcDiZbAf23S7*;EU03W!m0.Qmg`PU]R(3qFD;s:$'9@5hiUEOmGjfUE/a6*4aIc3&._4.n+>b=>X+k2>P@>f%W3XKb0bR'MA@,)e@2]0\p)O"c&V[KrLr?(Fi2mdB/!%e&D\m,a#,`0FS%eb:pUW~>endstream
|
||||
endobj
|
||||
31 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2081
|
||||
>>
|
||||
stream
|
||||
Gatm;gN)%,&:O:Sm%`<T;$%kbl'pBdR<G)]NKs4V0<b`(aRGqnioK]X(=\&&h8F.nR^"<'>TE)jHZg\a_FK?qX,?-(-2mUBP#SenPDhq(b:C//Sp`X516Qo,8]WI2\K[_f?D)I_<o`:8C11SRalGpie0LhFU#RE<'9:Z$T-5UHGWKZ7.,u=gOmG!AWn_#;M+DZP9?NJ7+i:OZo648ojl5t+qmQrZF\+NM>HJVmhHp2q>[2\IbE'%.IM[f9nB^'9Sr#Rhds:^ic"a1^h)BH/or[V"[?2ECC?S]tP^[?3[4bduo!Y(T-$#2[M'e?4E>g.uTAi3*EtTLWTjn[+PF>c4O=D\X]T@(bOj)mJbT6[^QuIY\Udlns)M8F;s/0YE>93$aUkhLe.T0lkqTq+\le]$l!rt<Qn!iPrp6=iAR5h*-\g(@'O@(;!a)G"T7A=o_LD.+U_sW_Jrd^%&mj2^m%&?R-$'_itK$:r`73:M.=CZ#"Ao&r+AEVS:c>HoZ7>$MAja>h0@V#qOW@G,4_t#&k&2?Ncj]N;Hl2O[=-6(ooPUN=WAN3"h\X'buREe#r!7as*f<,QDnf>9/dQ0k2D4?Rr,321u[,jZ^hU8F'BW1_"g39*+C0Qk%B\g%\"mtPF4]]!36C#g8g\k8"hHu13Vf?;M(.TJ:4Q?*fKdo','4/$Olau!\'L<Nn^%:nBIR)H;f)A')m==ZAmmOW7ER3,?!K8#2k=\8R`VE6WcHmu5<?cPud+AXh(US=%ja1>Z(EBG(L?%t26NuCPBVPi?nP7i'cF1-a2dnd0oM+WZMjph9P%r_`j1uNWfZdfoO([V034:&%)c1R$+RolfB_``<'?_alA$j@a38jZ[jKEdX(7B._+E$qRhDU('%kP0=gZjg`WCAeX!Jpq@9m\u/B<:OkDLp#t[^SOc2"H.5O#2-Rkl@H#CYm/]<p_=@^gdDM]%)WiL,+k-:/h#6i>(,@IdPfLq(@ad/W9#s$`(6A=<W$'CMc/bc$?T*JYgj3G$cO!G^^,YdKlDZ@DU0O&"#a+*r'K6HeNmI?quD6Euo$]E1lsW5@7]j:&XV@P29R&>[RGTq=a[IgK9GlKK2B@kVJh+m;.gapVnPDU?B<thnRi%R>TuL(Km&:peoPb5K6c>)IMHRP/G9DqBQYgWDicJg($S&K1O%X8jTXb]A\caB*AXEZo0IZh`c%MUf0\]X)=0CniTP8+IMK*;[tLf2bgV-*A'1P0'oN@)m2Q[^S&@0D;QqRg[Q%O@c661SF(o$VY9-Mq!L_a7%'3Q['MV0ZOSjMQq^eDnGoKTLL<Lk5#(I#b5-.nf3Q#;\"K3FP@g9)nZ`,&b#NXpZL^]a=1/k0r5Af4">,@GC(jD9L)O@#6`%C%Vde]WcVTJDiYPRN1hcDh+1qfFB&.sFR#/n4@9g%>NjpTDG$^)\_N/28YZ9fcGO',jHJEHk]:5JNa-HP[bBp"2,SqmDnr*Tkm!_[:[IsJH6dD0JBZb&<A!:2D7Wq(@>>@U)*q*.<6oH.6\r0sXc%+BS_.d<M4CS`tcrpp#dY6$7*V>14W6f!W]4LPfrcur>QpP.P3cN>N];F@p?s:(d^`g&aTFDW#V%"Kn2&\s*c&smB#-L-$IV2OQ(!fsP0:VB_kt*]01r'/Y=6[O`juclD4h$Li\hCP=dVWfE1$<$'YTK.SO[2&a$dM*Q/t'G1(8cq8#(u+h0?.CYDICXOPeAT%eU/W*$rtURV9Op`\hNAW`a!n6;SJeoK-1pn%Gpe@H\\TPI9F,/Kk\*i9M@<E,TGDPI^*7RH72'_FY\Bc=d1*;@l^?;LP8m#W^glr]lQ:KXa"TdmDWn4MnBL_0)?!hBj:!gp[B0bZ[91@8^!^L;.qe8Zcb[Ei,mUu'n1unF'!;:]m>]XU@cEXA+3.O$\[u0NT4\r<4`V%aREuEYhoX3NI!VT`;*gK^HNhK;@)Z`fQDiKCk@LmhqGOTCd&!Vm^SoMg!.>d`[3M+mD1J]]s/Y"*D_.4/s9>9ENU0""S6Ul&r;RPlQ<c#d/?cu&<*Y3]YdMHN:-%>1NT2^L[c[(E.\YQNUn+@a]p]BV;\\Ud+R:f!9jI~>endstream
|
||||
endobj
|
||||
32 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2052
|
||||
>>
|
||||
stream
|
||||
Gatm<>>s99'Ro4HSB#F^[$'P:^9JA5gO[d>]!Dp[#[9mNZ)uplFT;;gZ=CoU!jn05K\2T<cDP`tF$gMZ7s'70K'9BurGa/TGVichn<m)DF#oj6g`8)b71or:,3CZNLj=i(5h5Fu`65EI/F]L3+:4DO"X/.GPCD_*LSi&&!sljN]/p`K`D/e;_R0G-'l,?daMPkg"N?sCSV28SZd7gToAd1#X,CJ6InJ>52c=t3\2aFD5<f+,k20nPDCrr#@(1BK`%6cCBNtC]S$n#')b[)O;lSnK#%D:E9/iFYd>9Fk-IZO@<'J^KOs#]N8Lt2HLkkbbE$5FTn;T/ucaX7T?u6=>8r*a+K[,l;AiL]MNB^l<GCgXT,<Q_F!A5AkIrn]J87,O5N;&u.mKNR>f@lB:)dS^E!,t-Nf]b*pQcdAWr.G8r)eUH;*B8tL_XV*W67Lr331\c0qaAgJn7[&"o[_'=JsaM#R$AF?dqkbC;s-"Cc/T;'f/\/sI7BR]`7<!"qb$ub:;:,aalTFabH!-)jn*j,bk#fOR.1WUQl`Kpj2$7u"`%10n3#!@ojR7QK)@<2ELatWam",<r%LmCnc_fumaL4Ra2djAIpRB540OIqGpSZ*+!e@FT\1V+\k5t7l*aKtagJ*!AX[AGBH@k4[o/Kc>2@!;GjNMSEWR1F)aV'.'\#ZJ*s=(D\ZR(AO:#!O11d<egMs,$5bg%(43a0F9^*7pC^"s,$]38j$j0csV$Iu<__8O!$pnW]>?^.R5>[YDHWrf1/\V5QT[YUZ<!JJ^MaVUs&<Q6&ZCloo^StZMb,eR(C_prVR7uRQBo]kmBi7BgYc*&QFCjscErer7g=?b/W/P;k1EqbZ-Bg-'*h.&'GfO$\Hr+G-n7n\>J`-k"A9iit8*R;5_tdi(c\JUlEk2kAZYs\P(>c5cNaEUh5J)VnVV5Rg20lm?WXQ<Mma+gL^hKX47)0B'I4g[XKGQD87+0I*6\X_P2>HcVC[m7*&SK<$8/o=:<^pIdJp5gm/L%'h1Ku`J*n>1IHD9:)""$DeIRQKf/<WE&eXrnL/J4a*/jb-oXP\Q\gA-[:#TWfb6o<tu$(fg_(UFpX<6L:"G?]Gj3\-SGLuLdRC8@dlH(be(1,kWWM.O'.#c"$0iY(]Y0OOjV9Jn:uLK#L^WR].&R?^AR!9rq1J7VcK['I5C<GBV;$e@ZA[1>?iCm*b:jQL=K2gB!hRf@OO`r_NZ$Cda`J8l@''4pGYdi2QPmDUs7n-A6Iaq5he_GKueW\hj%>ZBt84g's8\i-ubR<m7DHlkg=/ljuIDY=:4lp]f#RlBJ#f.667freX&;Ise$\p8p(idJ>tLbgZ/J.h+(pPFS-oFKQR'O>5,\eG]GX`W;Gf1mI"dj_rDkbCAQ="f4d4;@5$@%Z/"&>dQh\Qp-H\tetnp<96=K&RLZGNm=(XWsrff%SBld8U$NK'04(a+R"IM,G"cC0[33LX2c+*9.8)Kb2D\([)"Rlil2MCR;T/j"C#Rmiqrl!i82Ppq!N=ICSPERa`)d("/R$9Q&AJ)E=_mqk.Ocn@?Q+g0ua_5Oo6od9ORMchZQF^>BH''`/M+/;qpN"do7O*WY=@%0$iJb?A`94*TupS8ms7(AQHj51qpKZ9s^hH)F;_,L#hgP9kE7/pZ2EhI\4n>)>0GrmlZc/0o"'\CKPa"4.G,NcWD(c*71YfBD.f$fjT)+--ol"pn;IL:^LknRmS`36huJ]^LY-EeTG&^'et<<B%qB:Tr)(dL.B][Zgs#djCHDdLcYn[i[dmrVUOGK;5lXQ(.Ic[BNZ)W9d*FG3CboVUA1o=M&3FlLGm-Me#cPc'4#h;sW#"Kh@\t%;.2-]i>alo7`7QV(WA`poc)GNF(<GF'iYMeXt@)d+PS>51:ql@a[[;l-#*V'ALM9U$c87bA&F\S+FoBSX+K%E`TVdKIp-gJ>)q>HUO@[Skg4?j/>'Mj%,BWIeK'u*;s):emnuWF<^OSe"V>nD)\#(Fk3Fi+1Q[0QAm=tjQJc<31Fe]m^#co>O:4Foj'Y>a!J#oV=MZZ0cg0&SUB=-~>endstream
|
||||
endobj
|
||||
33 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2097
|
||||
>>
|
||||
stream
|
||||
Gatm;gN)%,&:O:Slq;!>N.V`C_2I383(_qGS;WXrjTVM("YE:o]JscC^'>c,;+d'd`ZEGs'Fb,UkFCA7j:pR6LX2\=H9S7a??+tVk"F\gMeRW70BZtTc5;UeSoA.$Me"GrM;7!]Ap%/7iJ^F,@OXn1MMY:Y,Vg_,,fU0YbGT"*'Rs4*NY%HN'69Ct#N.3V@o3P*EfkFcKOeMHGstNc?-hZ>=)U=9RF1+h\t+84;W-QZbFA=oqH'-c7J!G;7QuIbgWM]hGoEUXgL%OdY>!b4HJ*J$mQO4]=13)?8ViU;\9_j*VCnr&-YK\h-!;FKj[kt3$,0>Cn-p_l%La5]LoXL7?iV\%;`p.d/-0@g"`oVeAL_6["*h=`WD^7qnc*<lp61H3,L3#,BFtP.>^^E/ft&t6m199gZ^mS_Z(Y-o6H(s%U>(fq#<>1'q_fFV8X;o8m=u@89u?.YKAJscS*(D=VNIm;AI4T$<'Q]%jNr\aCHaQeq:J(XqFRJ"?-ci)]U@SR&BBPBZcfh"FU==<]t2(Ag$N*.hiB22=L<u7UT[eka4VdRVFJ!mOH24_]H5b/-r+fKAU0"P-/BWu5>%gMo-hqZQk$kA1D]MB15HGio@0ZhR$9=\Rce&Bac(<N&b+5WL'h"a9kQP7Y<i#u.:#_em@(2]m6WI"M\UB^Qqk-_V)MACqK$#k]%$bKL32&5P1GVDkW^"i5dmhpoEMrVSCd&O[S'sYM/98*F?<#)'#UTC0)BnLHsr>L2AOcFD)L/8]$i2-1t,XWm,umVPK>r/4`oRgOMolW^fE]fgf"t=TLpc$>c.'2Ct2!LS-N^42-kt?LgSNQK;n1_H>!mUA)4X08dU2OP_`\s^/s+E[bkj3a0H8u1ron.J>;C-ag47Rc+mb'gWice&8YiWRQR\jpm5KDJWe.VHp_"Yn&c]iV#tuNLW+tdZ\DXo38E&JCOi[nK??!+Y#9de6`\4BE!\d57W/TM8$>YEGnmK`-QFUd50g.Enp8q$iVM3nSLoIRGGo7+?+mOL^Tb*u:QYoaW),>$@hMXA\m=er&ePrPatuhS#Ei8+,WUE1n1Xp2.+A/X$+/Z*N"8mDJ3K.$6WDc#e1,-L2`bZpo7ptQ?O0j!oKnab?K+W4[ao`Zl!m/IYOb1imV;r?TS<''2`?m)'^2"WI>)dS0hWK#`.6<)hi2A:G:EN?B'Yb)hmpnuds:0d0s/hEYcnH^6,ETrM#B3$m!5@1H\s#3bZH?&BL`d<CYLCb,Xoc^$ke7H]c()8;gRe2+_U8^Ws0]U!P?3+NS0TS'F"I,gu;:,6[tb*<2,P'n</"1^OMQAe*G]KZh0cZ?XD[n9S"qMUP;(jR%CHg)$<0Q)mVg,PObibShOF9VnXku;r9r0HDdq;+2YA9CqTBM=rG6f_`Oi5`!L&oY)[6"ZgDqP,AAF;TFYWHbS;b2/%[O'G3g'0$FD0;%>ee?'&j#VM?kZ.6e@]D>'N]``kX^PbeQ1BKYk1/g*P*^\JtDgZk!\!(#4a7#W:u3N)#.r_Rn3H%,_bHf)l&\bQ(!BXEHCRTTu\V7\:?9fEHD:bVs)+>Lj+2AVro+P=J32.bu_%,gt3H^"N'U\3dqg%BOKTens65&3\@;_**VBR.2H^"5E:kYC)#">BqMq"6R4qg2_WZ4Xf0Z5J4&c9Z+5Q;u#uO[<c[09U"LC%W`BLc,A.?%s1N61-*Vnlj_25:H`@n_edr7'">skqJ`>\+Ng&_o!gQ5/D;_$,R#HtL.,HC&4MJWD1;s_UF@%[d339V9a*/dG]CTJ.:P"@qLPN*A4e2D1o:^r41fc&ht"#DJJYa?*@NWcoL&1RGDG27jh*PASIZ(F_Au&-FT)'ljh>'n]#9b#J1;ICeuc#l$`;EP(RWJf:^:;q_L#2Gc.N?hP?A$,JQUsd<Mr@m6PYg<EPi?Mh#.27KTf`heSHc=]Z3`#F$C2%-Ya:>"!tc<EMRS^iO7X=n]p!V(9^2RBg'$;Dj"M7otoN>1?BE+*6EUmZu8/ULWAl>J"TK%VXaA$Vd&13s1g;cqotiQTA"XC)%KpFFV.;UqrS:"Q<q'.C9I'gVG!Fj?q!%_3bYrL."SFd.2@n%6>-<#QF7aE~>endstream
|
||||
endobj
|
||||
34 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1866
|
||||
>>
|
||||
stream
|
||||
Gatm;968iG&AJ$Clqu$K-lrL_oVNm/VO=5T*4[G(?kQA!+UOSJ2q)-k"\;7M/_P"g,?pn``BM"aHn,C8?S&j&-,Ah(r;M]Wj07A?Mb"!:O)V?f]/q6r1)2Sf/>Y[Q+C+^]ctq#X4Ie/mA0_:K,md@X$p5g[MBe?>Y^(AG9(@V`_$#PC>>QRd%H(MMqBBD^/$9_b>`bUe?,GUma45M/mdfYl$Jo+)f@>il*%sK]r3]jZr!h67@Jsq?j<HXd?Ds*e,YAQs^NE[dR]:FJd<:N;DM2P)RlS";4FoGog=Y1[RFT:#(/Zh^@f]!:;*ndj9^mX.R*[u5+sN8n7#kXt</`,J+SJn]R.1[1dJnm@&A]%L0MW60n\Y2'[qXP8&IA]$d(sEk\K%cT>6hV%9N*rn!9:DVRti_p<$Y^<$7>68a\nfP-qPMm_c&j1+jr`%K%VDOT,IKWFYoYIq%uRF;j,.&=]tQBRE^[g#[-c%EU%u4+jgZ9'3JOg\c-YuZ+drh9"BW$=-mj/f9Z_K*;:[M$/PK@_ETI?iG<Oba>(^AVAt6#H9^Q%KIBkH9i['X$M\pF5<;<tEN56mnF6$XS3.\k`H6l#=lKPFBMYEDe+!r"<_sUDdi6o')LU@-k/uDcSVGiUO?;2`h71,d>T4_!``Kak8q=P1KNl%PD"15dTV41olFGHIJ_Wr'd9rqlH`SsQ=P#11TJhf'`RK*[UVVJ!ONuuLpBRDA9r@r8*d2oY6H.*b1)EJpW/:j+?;1KF:XuI@#K(054=3XO5b6)4p^iHuQqq0:&SE5K?m63#WL`sd_i"8u`pT-t79H!.`T,?V16-"&il?h/EZlo1PWpjHr$W![lpnmZGeP-9'1@'q8I&?-CF8Zr98Slg'g?h%Yg=N,@2KC;`c6riF_D?ap=6UB_;&PohIC$4311Br7jLr0%a#Q*$EQ$dfh>2fcZ'A$l&!gCUO7-F%WO\kZ!`s_<4pjn%\NKL^TUW\Lr-S<4T/fs`YCs%Q*%JVB4FL9k9lfh@.E]8odT/EqfJ3jdFb<.'q"s9QdsYd\IL0soGq(W;`cR>@k1hr`\r'Ip-iW?CL*E=3Ot)rJ-7VW12WlrNQo+/"865M,@Zf0Ku8q+>%\W<QmL<_5&Xs_2p'+M([/cV[]O\;[]#neCX_(nO[o,faUU6A@oGk]ZUt-BkV7qd(f/-elc)`!2U%J4;T"SPD/DBqL[h\Qm2S'P"=M6_+Oa'Fg=?sE7>mlEA%q9r^Iu*2U#lQa^XZ><<!n)?2'0n3(UB[/P:u=M.8/$D?DYO>ZrM!Y9X'9m>`[dKU2O]&0eDdL*Wb^3rdR-mfoTkaEmZ`"b/8Md$"r7kh#htBS'D.L,"E?<)O#a<%]#6ZjLF]M:qh9O?Y$a:+#p"nb:3G+Q%MmtjrX$4?@]AQ^m"\9I)#0Y\-Z@DmMBeHkl=lYOAh8jrH>ScFSS&V77th[RFu-8@!%iae)3IH6b$a5,_=-(fa\@u[2%oL[&nfDAD"s[P!(T#YU/'I"MVR3]"PJrI4/UomoKS'93r00/5AZg)>oo,?B:Z0\5%8AhT^?Zg-&AO-`-t:_qph;6tgsaH,/?W==bV2XKM86]eDZi<O/PH)77o-V=-Pq?;VPj]m^7h4SdM5>f(sK$CdKF+.##ekkWAfRsE6Ih4K2u'fNtRA2sKVh!TKL!C"_H$DJ9km-ltEmL'"+MZ%i$8s^LCBZ:9^@7"bC*R]8@"DSVm#-Vlu:2ljJP^kFTIJRkVAMu`qR:fqKIKKdj*8Ki]R0>#o1Uf8l&Hkuq"2QuD1o6ecEM*4:4Q_JaLtif_Nj-@\4ha"F&U]/V@Ia+tj1HeS@;&3/Ri[(.qPSm("0cq.(Ps&GmVqtD!Ik@WSG`][]am"~>endstream
|
||||
endobj
|
||||
35 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2265
|
||||
>>
|
||||
stream
|
||||
Gau0DbAuCH'&Dk(HBY@c94eJ!SXlpYUn!TX72k?,["13(&TUHp+ushh?_>g*F!SpIk:3=KJ/f.!3dnT/JpdJ%pHL<frj/IDIMO1SXg]QB0;S7ESZbiW:H[To//I<'e.4<+T$6s627o%Z&#UW@U+cj$as'V.;+rb<JhR\>6dtbZ#nFobFup;!;O3DinK7gU`Loj<HZ:Ag6)b8TGJ^qee`Fd'qX!D>=7=GuVfTF5]*SOq`Oka!en5fVe+ujD^D[Sk$_BOGaAOZa_W:4]T5;4pFk'VUDnAJ]2aJ^4-F\(AAO:<O[5An/j[Ksm=^Dl_BcN5$`F7cHpe7n'UJLg@)+!K^,"eTG)/tiP#p+oUQZ-P!3HNP3nKMV^=55$rQUDE$XT'BRBlASWYTu?G='GGUl;o'29;^ur;ctLKqb1XhArIlB&fHB@NS;XnarDCK07Bp^9EQpXK(Mk[-T6+q(P7)@NlL6k3LGpsWL(Ui<aL4qAdb:r\YucJdV%C3$hVS;4F0fTDm.dU^ra2!([JQ60@)8s0+O6AZ5R0:q6LtgIDVR_:Ib.d1k[hS`*qEN011Og$KK?'dk&I,6.\o'+/dfOqu^.ik;*G:8-r(A,*B)e/eOXsE(ZGmiM4_28#m.HD/;iaZdk$;W+eg\pNtBRDVsq`ao>%;gSRmL!VS"C$e;5YD+[6UFL'Db[aVbABQDC-htGK"jhBs&lWViM+Ik4`dUuPthLS4C\_=N>5Ys;W%O[53Y<;YUYo^>.b,Llb.^oj$L-DNB055lY:hWY?.n&ee`>>r)%Ia#S]VN]YmEu2&iBXHo%A6sGqC^'q/SrmckpG!kQ+R32eHN=2Be0s9C(2,_.]o'HoI84">Nj@3)bUMSW\>BGA+B[37$+(SkFiV<OV_PJ`h?aW8:1[?OCI-kgi.c6h6#P%ME5laBSako82KoI[qK/QL>-e^VgR$E#VScuPh4!?fTQAYN`AW\4//B0qX5r7:;53-k)gK6'22lnZ:JRmVO+,f+Ms8iE9JS=2(j2=oA.5=HhCh1.Patad:=4Br,9c#:lA!>?p)u93c)`56/nX;[VF(T%L+%fN@KA[#L),j<-c[7/SV7LPH#H0KkWEp\o"Q!4M%aYHV,2oE6rr^JHK9`?CHDa,:Bp;m+/PGZ'?duDd(,Q_LeDt/Noen^U<sA!=0Lf0NX6J=!IdD<JMt,=4c/JWkhR"hBakAN;_"M6m@*W4K]C]4%jEJCi)iU9+tA?^4am@nb;4ukUco>IadFo$-i#A1nWjJ#j"%bl="_W)e+<NE;rV6qEfT1ij_2IY!0L:l$fg@*2\tX9\fAi-:`Vll6;pY@"3hA#ame#8rs1:FGV>M`!kSS-1gA^F?)VIH/W0I*]W/YRW'Z[L;!4..?crDA<b#(k[8SB!tcrsVF:k)8'K%(G;*h*T'kMVg^X<4bC%sn[ULFtF&aV8DsXg/59nEn%dJ\a5+7erqO)C:I&bmfPj@V0;&]ZWeqKY.pBY4;_/:nGNqFh"#jF*mZ$8f8A76b\Gl0<rE`.<nAfbYH!'A(=.PjZHNPu>=Ws_n%3@qa0#Cnj^gGm>Ke/[".(MX5TI1V=9Y+[=s=A5Qk>araAa$di>?TK*DVDA%]p`PkUQl^5YTp\+Ra$J$*iNrd&X*e8&oJNQ%fRR:c.<`6RCXmO(\-'ZFq[VUALS.[b]C[ct(7LM/:KD[7Ca@B6kN\E_"@;h2;7PK7\j6tTXl%UeAJW$g;C(38KsSlCn,kV^Xs?'A\<utaA_BH+:1;ct1Jf,CS\s&_Df/SS5N2u_cc)'^Fa<P^ln7j=.Hnl_qToP9M51R>-Yo.ZUM7XG>LTbIY#.qgYQ>`:WTk!f4B8s;oTm<[XbCRJpeH'pMrP#*)j)8#G&u#_,=L7j&[1P#r;&-jQpcZp6Z&:.%"a$N#+B%,0@JoQ'/4,"',rW69mXAg6?VE8)R3.<gmP/H>/-'U.>pCT`b^dDF#\D\B_:d0JSnM,XtG3KF7nt`q`4=NkidaU?L_!J&Z9(m];&&\T/QY?71Y^1Q9!E8$uc[%A)OM@p)9j/>&a-`ROk+/O8:*o>]8?2?`=u>XdX=s(o+rthl%=^+T-,#9HsqRj'd.FgCIoDD]8V.&H;eS(Ff%(R)A1-)0bmi%%6M90M>Ms-W')OmX\H_2[#Y@b_2!f#CJ9I@ksXOm*M-hpID_=l,c`8TM*QqIPuCg0:;V/?elt'lfi!(o+Yf5rdEE7j^OgSEe#%cjmf<)D/RDC</E*<bi?f:]gmB7rEB^YqN2HTjDPhQP%"~>endstream
|
||||
endobj
|
||||
36 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2121
|
||||
>>
|
||||
stream
|
||||
Gau0Da`?/p&A@B[qC&sdL&eaKVJ(ldP,1]Zq_AtT`/Ke>"O0AYjm2L<c:Ap,2QP&dW\b\*fQb*Bf*sM+n]0GbX<dXj6,/sF@[n2a@Y#?BidsoAHXHM0k-HmRjKZW>.4:1Sp'*D?]a>8t9R'tg"GLRRnMlJ`-_:U<!%SbchX83.rik*n_'uWNUgCF]!-d*#9,MqOb#:b9(S7t;-u&_Xib3=RG'.kO6mnmSHaFK=j5TY%_rBn>%5%RV`S6@qlbVoL7@JI8*5oJO:JB?+Wp1"`[Tt,iV$PaXB82_Ba)l-tQ-NBpBj$Dgo$T3a,!fNR"gK0.1WBUQ`l*?8L'To6KlOU/#"$:2N!COG9*@%s#j)>@m"N`;+,#Dh0+L?S=T"djdk4'3$o-'M5]`QQ_pufYOKLXp<!E?\TC0qUNGV9VbekR"/\8u5V61ec06n%_-c1dp89Wt%D,[q@BVW==8)*2N^2YA-'/,2==]tR\1)k)qPKTDK`dNc)]0u,bM+<n*_8XO:MlU&C\Y@jQ[u8_/B$O1j(JE_FWuXS]@gWjpJ&ISu2lugrD[XPm2u/JrIQ2Xp^<(Rj6'Q[eOs]s%d-[n-&^-=8+3+<aT3:*"p79P!2\LCC+S1A1e9<<MSOq<t4Of"XbJ*34]5(QMiodI7U"FqYVhlMBe'Fr=!]FFR<P!R^fOB1406uJ3!<5OcB76&+E92s<P=q'<(UB5Te!/:UF4E7=;.8%eU;#&+Ga"U!_LIEjFV"1_%jS1'\1]pl^Z-rtAC@W\ASl._gOlW_^'+R-MM*>e;luKd[pVrFWg;4qXSP88,#k%d1'>gLn#Xc7XaBtZ[rJ9LeIH]mXBm;)+-HVM=_Lo7jVB@1mnIf*nKfuffg2oN_h1GA13XK8p0F$U!.4$t<7@5#jO[_e=uEU]*57*1,KYd@<Zs)kRmu3qlJjg':k`P&<D"*QJCJlYZHgorejl]Na\[0)&aV6gY87Dacf1<E>lNY:WHZ-V*725NlTub^X1gqTXkG6cWp;uG^us%'ms&&3%jgAR5,kEj0l*"@MjldColJ&[1,n2W5;+"p7]c[T%da!]<=#KSe&EN9j19=9SEmUAXbKJ\!oRa0^m@+.$hXsVEj=@sD2YR*JjcV\14W8@WA0s6'&$`2UdoinQIol;A=4Ts#.aqDJkdHXL0c"QErY.sqIKD6!_AH",)oJ.o&U$=HWVt/&)cXPCIjM]f7^41d4U;jEmKB)jIU]L9nGViC&>,f-X!,O).[&cg/PANgefQi+f_qd,T4HuJi`*WB`F',Tp1GiRP+hTN1?'q;WV*AOl>O>WlS=)g_Ec:XC\7.Pmt,ag5:diZ3TJj26KqhPIlFL]%E\2Yem]V!W/d.A0Bl/W+OF"4'((TZI9O/CFf?P(IMhc$e!&?C$G@c%?#)@RXggNNHnK/TQFW6Qt[r0R#bO"QHq\77qqr_As*'GEQ37n(hSU3F</uY8ktA#/d496HmcNAD.s'uG]'/jWr1;Dc=skj$k(JVp#S@JmLG'1O[Q-PEg=<Mdj]#W_V&EKX%X7t6;V>fE`<lCS&./83^Er19\K5qgX0m?g=_nf*Y?n4Slp`9J`KoK(3tNTY@O\i:C8M2]pM0[?^#\orkD_mT=g*"a2(2Q=E_UE+</oO*_oN8*pdA5("!g=Mf;pA$@HY)5Sh`)a.BFa2q^EQQ;d=UB&)]%_o.O=Ehj9Pp68`2h+9[qUBb:(XtnabC[D>]B2eFc$p(B(+K[/"!lD+;\lK<lf_.HolT#JG&XoHrcZF=X[tg>:bq]$;dT,92hD!,E7jqB;Aj)\?gn,PT-Q?unSP`kQ4XA=I^=0Og,15';,S.fU[)U%I\a_:+?^a[t>C-VsUaXi1e5>2]Im=&*#r<0Ok/MR,*mp,.I[:5]I=(=r=VenI/c-:d^`h?*.et?4B>NARs*0]1^l]bjh4X5(e04$_Zn5H/rKd$L1'+A14?lNXX0S-?p3.S(/"=?u>rTj@p8n@dJpM+5;1MU;PdAaN<50cI5)rfqkbRN6XgJi6XDi9fiO1E'g^,t_c:JP+rkPeaNWr>0613NjeYWG4F2+c\a(O!\HEFD%R8;^Q3!"@!/d'\TYb]fMX]h_5?%0)&eNGh?_uA[43)=!THiF-1r3\r~>endstream
|
||||
endobj
|
||||
37 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1330
|
||||
>>
|
||||
stream
|
||||
Gatm:9p;>1'YO<E]VB\od3A*D2ojrMV.q@1b`J?Bd=BFh_2+Al43=\PPJC\LmUeIUXtjBMlL*9-T.,$[U$b!,!W8>$LH>j-6$<ALO8uS5fOq6\.mcCf7MH,&4?mCB=DE<c?*7pi3MAVC"5ekf%1.s1#ZsFk+:@TE!QXpb>SKEt[+g0u@Wt@71i.]WdK)uXOXOciKnT9?XcT)]rUG?+D=;q%I+Z='*hKeFhe.3)Yl?$(hS4^0:WVB'$X!\k?J\HkP/]dM?2dmdh+?+uK"YkUD\%:f9:7:i0G_Q$eONbu3g[Jtm7#GW8g5?J)?mA^iA&=cHI9[CMW"fYUL>,P@\?\b`qVl2[6uaE2_bR80aSkQs,i'l&r.X%=T!XSD%;$Q/"jiH5r4RUrRpX/JZN(LCP;bMT5<&F`hfJ[bebKV$bC,Y;J-5q,[\Ds#pn*!aV9ciQM*R548jn']"AlbJO^\+,`0lOq$Cu#QGu@!EN#q4o;-)NTG>qo$beUc&iZ\-%,4sn*7]jl71'&bQpI<5BkYI9(:oZAX@B?fE)c\Pa)=*#JJssJ$21Qt&!f]"j/5?aY]]6b:@eN?<0KsTF(lVCriC4X96#R$3ns$[crdBW"^O\E2Xo.cGV'A&H8gHW>FNC'-uj=qP^eh?_2nLp(]f?OASnVEMg-8-h9WPp;"'#ucl#s!`RWPFBiV/.;0"oj[:iNprR,Z0TiHe5LLBEfYVunZW6okG=fo)2ha\Y4Mn;&@1Fj$?H.<dX7SjF4Xa)/&QM1hq,7tE2Kddeg<pDK40pm!,]01qgi]Xk33Nor4el@tm"i`-#gRQ,q'R4@s1KRb<CLcq<<@=i\1XKQYjCg&%a5p0c&rbJm1AL\JN4qIEkrY;XH.hN*gPDYVV9M+Q_tq4?.Cr,;GQW/qk&i:%p4L.PnuQ,p`\XnH9W!oc-0Z*V$[PL5;UF'*@YA.jEU78/@=9No(N/U_F2Y!qFH=V3C>u(Z;/C!`/,9^A"\QFeAU1%*XU/SO/qnol)hjW+F!9eYIBPQZ+rARW^_8c:WA(ER(N).@Te1P,7rVbVIN[7f[\g*/X"$"JRA!k&O-Eio-gY2j''"3l=3eIq&@4ZGPtH1Zd:hr9TeJ8Vqb(Gu8W;V'H`%G(,!(jI&dRBOb7)a>C!cU[rlaCSg8?mF8,8%5PB5RR7*_1L6VLb<frulM.mER>0c.9QF#,0ZJok`):G$KH1M)*>D?WdXf1%^GLq9fB@[j2n)rPCRK6)JGgj!PCO.tS;6Zh[T@JYuGLK,-h,"L-'VF3U)K73_MWYr;_n&IpqnOk"?nJ\Y\\G8p\c%$Vgs)!M\mY1hFrWO5U8oF~>endstream
|
||||
endobj
|
||||
38 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2364
|
||||
>>
|
||||
stream
|
||||
Gau0D9lo&K'#"0DoOY&8W<>l`S%3eX-^uP4nN*,lM_?ajbEsR^)C1N8q=XV'Ua4*C@ni4d:2Ef0dBC/h^MZkGJ04$*QOV?W<qO^^P%7+a>EZ7IBlu`)X-<Do+TklCP(_&3W!GtJ"b/dTIF@-6>XfooUPP'W&Rh!$']pTsdhjSlh7bkbVYIuBD^)!I/JKD"%H(/F@nBR\(1A@8%jU%W=>b2HH#^8RHh[@$iu,NnV_;]7;&&$Lj8#V'fu60ho`qU*bT!el:N%<i\L=aCgY`"4/]U>iK;J0gE,)nC'"KosV<]hZk`'Ft9r6UmW-VlF'M4ak&W%]kZQR6E$l<ptP@,2c-l.A-Z=FFL67$2t#?$R.JsgM/3TJFRi]['&G[r5.,f'ZEs'<893f5.T;?RZs0F3m,lF5W5Ba=$/$NT\B%!LmT-DaQT-?2<jFLu&uSdp=m+2\D+.:a^qI5p)bPhu>TdXONXc%B^iqiN28aAYp9CjsU7L"1^$g)+$20Y#JBqD9T'm5*kaLGr;,0@%nH5(34uILjTq_8NkiG$o@eL,H`;Cf#puc?+fZ_o8\NF/Bn6dmE,@OlN=902Lu-B&R5!4cbbr?$[6A+:J;XJ%h&hfIL<khFe!LaFj98?alo]1[8^4W*AR+7?saiPg7i`\^n-'C+"$UX_DUMF1)uQp0O(q>N%[.N"u++3O@ea`3Gl7Sp&D0qOOXcZ%I23]=K(YDt%-mD/A#H(L_)O2*\djbjc1qR7IHPOMO,`:Fii+X'tXLe5Vi?m<k=!CO<:oZb7fID:MUT0Ke"1p4RplD+j3!HI0?uoCdW4]L:A_hk\qpNC=JQ]TUC<mW;[t`Wo&Fs65)ee#>2u.Ao^:nB2\>-l1@tEDZU,Na3R>TJNTVgDmn2e+\g%-KkM[CGrh<pU!r<kZ#gJ&[0=eP>d@sU[nsEnN7sn<(Bo`"VYLY+:ESE`6alZ]AW\LQQ*Ur/Ih!Beu87^TX$K3NhjcJdnJ3W9q(\0oLFcicZ?fiNMRh`kQSJRWUsEEF@Z@Z61)cILuT^9U6)I,&Af\D@MEgEa,u<>Jr!.12IGET'#VM']T^'d2V`oHf!B?6)p.%,WC2tH1(eAHoeI,:=Mh>[Vs-eu+/k+EdnVRH?bN=dO#LE,44%-05astc4]UF#5oo1'['eWN;J"Ek!$NihJ;#aM;6A\cK:9,gCCQIE#"R<=\0@;l\f+6'^p6Ek(q$S+pA!R.#pBGCDb09erH(Q5T!7JGJY75P00Sj7d=!ea^/ro_IJ/15?llK`JuPWjRW3==IugMBir:)b'#kdBq6q-K96rK,kkV$K->5Y)Xr\A"DF#7Z(Yh7(m(UqU^cLm:JaY6BBBhR=X6,gXcou\*;\!k9Vg1H['q"Q>>pmHPWi(Mu+&s.RYdRQk)h]WV/<>5Mhl)E(]R[j=GaRTD(!pD(?q5AHpWmj)Dio*sl<-p#m-m,B<4^]K*GkA>4qEIIC@JSc:(M);4f2l6LmEqoP53<*d#X'!&:.;ki*dYjD>dK3YOpK=6]dX(9j3T`D?'J.^r?O:RQPnZ%rn%S(Ei8lfH?TGEIsRAJ7(fKoI0:PQ4J!`,cC+?Z"$7>lXd)u9CsiS*OT2-rW(mH'%au[L+CTial[$\^pVC2CumF-4<%%oo)\<&5NgHBLBh&&_a1.05StO]iJ1H6VWmB*rk;uD*]&%k2QWhRdbZt'mi+1nVI>&RlGHnf[64M9E,=_Smk<R]l>#>cY,p7GYoYmb(oEZ3*=-&8I<.YiU\raE0-(aAS7ao):Bn2:1^623lZq@XiuhfHenU,Te$Z#2mk,-$SGj5>@['ioq.o+C[%V&&K*.u*Xm_4NIF[6.D[.CIH:`PE0b.)^qHc19DOQ1+"ENeB0p#E$PJ=)G;WH\F&-!,)*T\IRU\%+]Mf9Bb/GSH:VBFQh+4L;uLA&?sFc:9h7a2Hmki!aQA_K4_S=NYhl8LkhT]sb1f$.Ig]hj7:?8N@BQtlN;OEZ4"fs)QCkOe__SMCfB8V]HCd$AOf<EJEVkpAQj<d>>R>DX=>0NEeJZ%6h"N2YQ8YF.q5F'G]gWLY0u*cJS3d**O0@+ZG,IgeM%2L)DQ^1$M.bi.Wp%oG>0)a0,Wf[2;GIK3]"CqO,p::m]ADU:@#DGq'K`d(\:1\FTS36'Ck#e=p[+N:Q&j\LrY1.sO_s(jj7q-'I7%o<q-/3k4#\pMdX!dEB:M#6m&e^&.V"s/aiR@U],J2]MEhCDQlfllkO<3)/=I/r?gRbY1E!2/o(5X-AtCUZ]aYhF)9[U2f/`^fg?=lak<"f(/qCWDVBVl,lldp",X:;Zi]h(eW+<it1FHbc,gj29qMD_T+Tja&mn0#VND25eSXUqNV88GpT`lN*RS+7_!$n,~>endstream
|
||||
endobj
|
||||
39 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1849
|
||||
>>
|
||||
stream
|
||||
Gau0C9lncS&A@7.o[-L"<hjSQ$RAPJ,nL\BVd.6ifF1:hB-_%er;(h)[UaYN(:3=k;itmUAmW6$r?]P*8%ARf:%W$mJ%QF?!h(c66s`:Q$TaoPgmr],(O74G':Bs)!I6uV5EK0A1jD"oTbg4-89#\t5oM*bA6p*!&QU;`^-'6/pgCKk,+sljaLu,2!.GYe/90YqH&rEVK$\R#b\NYQ?(B29hgN)M4JfB*\iIG5_Z2W^k9&lb(GBJ$NTMn')mKQBD]e,0Q`!]l^\sQLqH>bIU%>_!ZF^QUZ-n?"gE9&OX_F*4<)'&t2*&k'j!78ao("%ur<1^d2e(^`hiSL?ShK([hN@o>KLkW"_H_]:A0c5S5P8Ju_8">A!\]OCgbA^\b-ncf'H.UU^B#AM6+jc*ZSePJ:>*Ve!:81*R@5k?1e`N]b%X,MF&q_ii-X-e3'elR&W1BHN]S"+ip5[$cOeOo@M3ME8<_9@&kK(%=_@c.LkJ(4I\)Z<Kd'4./Jp5-2<JrsRh-("oCVBA>"NdHj]_>JCoLBU\hLRW/(M$!!?A3!>^\9[4]FYd'hr[B%Yq;a1n-:,]_IEd+d-Jeo]U$k+@N"VUY8OQlFtZ"Zt/B,<lS$OW)YuHFphH$27-s?pekcR;B:9E3&]28WsZNC8n]^1kR(:tWeJNW=LD+$h,7gQ'hF(TNNHc)>*9W$N#=O/0^Kl#pCP=XgdQp!NgXlb29DC-RdG1FgiI"q"t'9eF-8\I.M.r#ZE+OhC,n'<aDN!?[]bdYP0drb#gC*U6P2,uT.c"qEPYMHmd8):9:(1/f@DKqgE'%)3!FC\@QE9;?#O#]=gaQ4$bbgp2MKQpc^PD)q00*$@q0otS7A(o5"j"$5Utu>oi:!U$hZgib6lAg!/PWDHo`4LN"."M6*4=.l'o]FPfr=lSE[Ai]`:#bI16sc7C[i)YqW2#fg+BdH@8&1NK_jZIEnIDVDo/o*2$n-\H'moYi+pc'mCI3aV\n-Ri_>WgtiLn(62e#W*=(:*fq)1/=qCEk8IXIIj*Pel4CQ"Za\@dK+P(g]r9:7[BI&TL-91V2#p7r8A&_@E=';UEok]&\lZ)V+qhgtQQ)o,;t`G_KsFqrXL7`b6`KPj(WsSZ)'0urR23`MQ*pJ3f^Gqd!%UqGgoS`M6T7S*V0U;,.0=o$n3q9K#-J'n6.P9.?e17!%ddrqd2P2f*Q$(s-W6GgM@E&ZTt>bQo5&9o;H]s2j3nM^6A3ZZ`91J,fX.BU7sI_F[6cO_V/MP'@0;DG%kr`b/q.2_P@;`1AR?;cRF?0p>01]'e%dI`Z+d'c/<H=?YaA-D_dD<pbUTXW!Rud:qV9SJiUg)!9H:C87#_Ok?T]>hi!(2=H:QZ+nJVT^fPOWdR^)JSkXISkOG8^YCPsN%UA)TQPQ#H$<G&jlNa8Ja>)/2m/=FI[pgb!GK7$qnJ!8e0fef(Akh:-JG*BP6)r5ZE3?N'r"ngKWK/D55op"M1@7=XXCN,;))4/-0(Pi'Ys2%USe^95UPjR(VOg]AVrXS%)5ktbpaq]<QE6lkV(=#7P>6ButoKmI9>3rOYWk`;Bc_h,WV`>8uN1eaU23j?5dERP-Fe0oIJhR<n1>=,m+_c<&aeMXY"ajFVk(*M$F4%Idd:EAf1M!LXmGU=C8\B^$,j2h?G':\nbGp&2$6BDC"/H\$/2k*'8DT4T(X>*UA?;f,)ZnZ`0_37UU#m*G0N&ql2)mMD1[\DC@bL3]C8uCII@(qnbimPUWU>iJn;e%SUUf9K8?#faRo/(<2eXJ,c[@Bdqg8:9aGu8Q6IcIhS3t+p=M:.u?ahian<tud?_+l^(Q&P^mnLbCY&@\F"#leK%DX4U)u~>endstream
|
||||
endobj
|
||||
40 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2285
|
||||
>>
|
||||
stream
|
||||
Gau0D968iI%)1n+i95=s<YY3hHIF_":(jIaG7QdLYS-?-,DhF%bt`ceK%)ljgc4Vj9G13qL_%9+&0qDeJ#N/^V#YuriPR#Me;!''W'_P,hS@\4m-0I*MhW8V63E."2@qt"0-K/BAb^71)0$\:^^XRYqlU0I5a^Rl3((LXr>@5F5<.DrYT_@36K0j0n1AL]\/t0,4/<G?N0k?G.Eq3uk0*Ru`OFS^I(Y&dq2-mG+)\:MQLgSiYl"OFpWZ%YK'DB"(m**gr:0>"<E,e=.hu;(A^8I6F6Q.`$<FV&_kkN,1)="<Gr!`86IuT-"sig\ht$9ONt8VjM:F\?3Q.Ypml&HV=&J8C#G1W83n*Dbg#2U]CFhI'*blAGFn%cXs%DFlME+?2,utK/[VWF%qQj!@ZVokP63(\k/Ci++2PeR+>pk:@>p$/pd(ppB/fZsGKJFI)i>Ate)>+)+?]dnHLS!>SiO]*s4JZ#B*p2-@Ws'hd)OkSXE41Vq$o,WdioAS66F5b@l?^EcG56CaaHX["eai=h80#gn?a^g1g5jGaLt*^Xi#1^*3GTcM[gB[-/3nQs>rQ1f_=9i)"CZh5"<OGkpO[@o5./8CEo?'ZQ^0_?2B<ZC9W,T_SImMo4;VST0O$fB@Z`'p"_;)K@NLFQEZ%FX&"Gl"CA+=OjYKCfncCd5M#=00`9J@2.YW\V4Q`lnKh,d?&t":gA-L"b>N]-m+)sWfp-PN8<LG8s090T8CI51h`"*3;4mGJF4QjKnM5)%)7**D>esV<A>XUoEgn5JlF28NG'l7s!Q:2rseu,5C1!=P+KU7(gns#?q8([?<_Erp8hVUPKq;$_%hdjhM@T(5Xdk\kk<M_1G>l,$!':u14&ts63HuQm+g_#'h("gM?E87;t?Q^@re6P4mL>cO#bM$uY\;jG,g\963jcI/a@E0:6KS0+GbIF6!Kal5mDC]K#'VKN\g,Hq*AWS?X.-gZJoEs(oe.5hlP:":d7>IUs`5Pig)jm9)bT$10MdZ)YX]gjjA]N\dO`3fB7D5!/phr"@V$mH(l[=j`.8iOMCJ8H+TL8#MF*D"GA0[tiS%S1Kp4o,jgVC=a;6CPJo1Wbc:B,ReY5ZclT'tEPEWa;OLkWUoDlR;cpdhuk)##pQ4L.Gc-$k)MG)[6D)K3;&r1]d,S3FUklmf[2Oi66.i'XPc[M1aZM@*"XbTsVQ.%Q%?na8eak%=Y?q7LQp($?Sk^(FMllWX</GtAD1^*]Or>bjpBUS'>W+#Ig'9!Q7U*d9YXfJ2J;Y"HBSk-BKOaYg_/:)ED[WMe_"Ve.$Be>Je>,9sQsdJ]ZMo`.SJOfmTfMN3f^'o^J09Vp2@kdbD1V]\6o.L!m]VV$L=C-YO13jQ'>@7MO*k%'s;;YBUgb]nl59<QRF;i5h:&!3Q]mV2SXO$lk;TR&7XWFF]='n?aCnotF<a4Ctj64RR3G<6f`9^I?Y+*^g,TN2'lOo"h#pbKZHj'm;o^F-:1,^]5(ghYDZk`+..,q7q6;&FtPkL4G78<'Or'6*fV6JWHKhK/%e[_+$,?J=!'[hEI:n\!AaM=Jbg=)Og2oGV22#(=C)I9u,6+f!GKIHuj!9#'No>3V(#Vht!d@W,6Vg8WbVI9j<UVr779-8eNa,@dt8?9GVp0fL*UZk[Vk570NG(&S'%60igu#Mdi78sr+><P5%d3/J0ClDr-<Z,N4FLK^X:J#@iQ<EJqYs+8!McA3Q%%,!lI4FandBFSa*hlb:]"4,f>1$oK\HDp9`\uUcj@#\JsK8G40'+=&Wk<PhuS]D\Jf>CXBLWY/N8$0QE.<8U&h8*6P]0VL1i]G%I/ainWg^a:O3*P/JH8uB"3gs7/%9iSZWJ^sC_NOtl-8-:)^&`!rr7?j])r7Z7oM-*A^kQa%;mai>4%#S'mC1-R]E+UT;EUZlXjl.7onW-Q>/P/FI305$'j^im)3^PL;LuYnCbY2!7Ra1]Ig2sfm9[K,D`,Ku>EPE0+V)Dj+"0Ma\L\rtMG#']:MhR^ksD3Jdbu&[2U0nc"<*jpMF`9.bJNk)W@d7=X3+lhF$,q5du!)%Q>_$I$8_&<e7O*1GSVh%-.G[i(#k(G#5:9I\[J6Jrh;CmSaSt0QguNU2Ic9?BN)p!r[>39RQkn945Ul.XlX?$nSJ5T1>U_LMI"E">T.gZCOY8D<$du^'tWTthmm6+[7(kp)qd7_9mlX,dqana:S3bH<KuX#Qp4TVOSsn#C3DVP`%3G3mY81K&p`;g%?e5OVb5I,N82rbbn3`_p8IU(D:CKo05Z?JIfY+S5um~>endstream
|
||||
endobj
|
||||
41 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2319
|
||||
>>
|
||||
stream
|
||||
Gb!;dbAu>q']&X:m\Vd8CU4/ZVft`YVs2inA#o%;)jl4fB)%HRde37uHN93AV-$@=;;CDLClnjVHZ1(i*f,r:&$u;"SET@)IQhD_3)/SS4NF5]"\kjaO%0\$=WB,9=QG@,PJ^o%JFqj"^rm<'Npd9?IdB-YoE4*8,J:h0$q9b0O0C#rQu,Q:MN!8A#s&05auV)&hukcq)+@diG(jX*a&a(Pl0/'*p#V"L'=o(;$,LV$3VdMQ\p-TkcSYU*>KH/=p[TMgS2g/Xc/luWdc@F,9:AhfQM$g<fcj-_]&PoKG@`L)B<"L#R13smYocAb+=@Z3cjfIbie$heES&JIT&VRjoK-^$<K)WV+Y^W`1P:rK48bf4gE#X#&*$FKqm\?TDj@KnKI9B$Nn]0J0)+,$hlDpgU/niR!<E!ilZ0Lp5,9C`^)'_/DB5h2VM"2&?c(b6F-p@_-6-feH.Mo/dO-;<s'idUI>!26;1R.go)3hlYj.D0`fL5A?%\0l:`hLUd,\eb4dt46`nl3LflB9fRG?_p7rg_`ge<1/rHN>Z_;rq_%>6BEjP4b,(aJkjF+-:;_)Zt2!$Un64267%,sY43&3E`'8%?\)B%QlF+GT2<2i_.([_K;"i3Ci<a9Cl3()e8;!B^s6`9L,Zr^\hFWT'o$T$fO39kI>'(U?U3P-^<N:61C9F\-XWWJ]1Zs(-=>M4@.PE_/ij;gJCGe`g"hk?(HZ"d4m/W>+go??D08k4g)1@**r?c"b<\/-e.622S>pP"@!C=ZJ==3n%@==6d&q[>%\&]Mc+t2-05`kmU@(HHE!a`QW5:/o)SuFZ+1YVo?<u\]dX-r7k7/Xs>U(<Zn!_>WQ5Q4X^C%7?o&T_[3#Pe(,`SajEn/4EBiY\rs\Pn.h.ij2YWc#CL.f(2^4$I`JXi\7nOq*ue[DD69?P#nKY-Y9YI]$>h)WU*6to4V@.+s"B\P]Y-WkDInQ1,.=2)mnQMBKMEM4!\C_!P.+Eu[WCuc&KIr0\M/YAA[77<HNMTa45HF5ZMnPG7GAt[XS.jlM<R''k3cHKKn!Nt(P<`[@;VoW?+=3L2ckN`=W#R]>1EtVOZ-F6ShB,&AMe=%?=b99KV:/tJ4P2.Nq%K.&X*!q8S_<b>%VtsFp?#1p7fGJ*\C4?Z=PP*c8&>c"_B0uf;_:KT2J8G1mb2p_&j2nY"$Ur?@^fm*5Hc+`6'b\=D'(H'M*es+FGpa<?t:>'&"pbh:2#&T<]9*lQ-PnPqJ!+1!H`JeoS,FlRoDmDjWF?DJe&D>eIfO@23$h0QcJl+cAabnDCpq=@$Pi!Th"\L?V&sZDl%&%+T#/2V(so1X^>D8QkFV7/qe&F7K6E9\*&f(D1_]eEO7k(L#*_M9]3=kcK!^=ANU5Ag[Ek[)i`I4sBJQ3SPKuI_a*OX(q8lPrtGRDW8rH6Zu9sX)+lQ7qf"$&07uQcF$5k>2GsXh.N9gnhea*VPGim@W=l*6l.KlW2,-AT3r+#+a];IXQc&-mZIL+^X#_bi*RSN15aI2,7q-ET"YeDNdsC$C`Gi4KCsV.DN,"06_:bAK\P#gG;k8d],H:R)iW^X*roj_#&r0)GT-q$2#t@!T6i):\?X-raoIoAh&cCC6@?J/NGZ0EO57Xj_3>`Og?sVE@0@hck9AP-N#5N25N/)b&SKo]%`d_"&_N\9e0;pH9SDE?(In>7#buB#=5$:(FVB4:8HlLE?VM\hL,>XnV3"A5f24>Js$@mJ#Acqp.ZkU)EG7I2B&o%:q:HoJW88RTNj!p-F;gduqpG0q4JB7E%sMqC_^;52C*8q!X_Vjm5GZ:<bNe'#P^6s*<T116X:_->oE)r4?>'FZNic?HQ,OrL)pn%T>/ESuh5,,]b\3dQ[POAK]jef)*h'ak-WEa@k2m6OPZA7=$\&Tm"1+e'MC(B6N4pK4&#oT3<G#"IJH/dlI*obKEs=MYL"'F<&5aSC]I\1g!0!-8ebK'm<8bR:C;;$+"-IoXHdKiMWGBt,B+Qt)/S.qU.T]O8[D_#?dWM\k':CMsp@jE`*(c+4iF.oDV4M*;q.o63an&CGeZ-3hjGYeRl$">KlF.o1QIeD[TI^GH+nMB%m4U>(81lBBHd*BR:':3Gpe%"E'&GUSWBLli&:`,uU*-HX)I4Ja14?U!li6I\q>H,ajM5[FK(4RZ1UemBpV5=94%.Ln,Ap]C!u(PhibIH_NS'6Ze;2=]:)*N9,pJ9EnS_<]/#goBREpi3J5#YsP2O+,LZ5r0_eWi[42'+C9%m5,GL;'b4LV?u@mb$J+::%S]c[ZogtWL76#MR>WXsP."]l4@cG4>-[7a#>!usC%.f~>endstream
|
||||
endobj
|
||||
42 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1722
|
||||
>>
|
||||
stream
|
||||
Gb!;c9lo&I&A@C2m*SB=Q2n[J;f*i@:7OA%g640D$-C8M"N(bJdpLu;O:k.*eg),-AidA3JkKP0/p?tP132p<9K5rP"u_hCrE/2HsY)j4!-2XZ).k$\u8D#oP`SG.iQm_\7*DlJ82QQXf/<Ja[ju,_<k.+=&!iSfdfR6IPSh!h80S:KnX*@cJVO_SH9@Z2mT03aol=7PXWW]>LM=Z^Z0>c0EQG+0Saf\Y`0'>L2?>\6cr-XPUX`c"GbC*&]Pa4MSRr`(gs@d<tgaST]]<>>=F\X[RU&"`1)79t-W_FJ6m[b%-niO\^?b8J&n87ns]nOW3;0KF>67M*!'%o,X/$;(s6tWF@Eq(IF<Y,VqP.F2/kXJW8SYH5Lk#+M`]Is0o)!>L3%#0urZIF-d.1eR+:_<OT1c2#na_e`picq]kbX*r$'/d>`pHfQ(r%pff^?Ae+E74GRG&ZJPPudg$T8rehAs$Ua&$8_dXG!+C&QGt\?^X`&Y:6oHPu;jk[%JKZd5(om28=nQFsh2YlN&at-B!_:>?FI928E<e<m'"e-%s6C]R_P2i=<@q'TTiWa=R3\;\W+=M;k+!o`.TE*X#fWZ6<+8-nXgb<EPV.Z)W"`4mU(pm<(Li]a&!&FAn`_`WPQ6SJTFur.mtL-DTL:MWD-)Wh^t'^91UK[BEW:.UY"_*d0"2.(\k:NaC?UF07kY+D8d2'&+HaeNm9l-YF-11:FA8`JoMMiqHTJAi[GKkMKu'e3%*3rU)UJQFSLimRJ5-hW>_uWXC1R*/VU<K*&>O_N#5AN?$,gnLDmeH`OtK-Tb=pD2BiS/s)oo_t-Z#^T\lIKP%`8:[;g$>J@Fc0'<Tlec*Q,ilfJ;cB.T$[5B[:WK`.j.%Dq2M*DpPEfKTk_m&kg+7QVIjGC+Qg)e=t,cDnLL</;-q[Q#+FMi=cq*U]<ia*d"^k^V$#TZm<2I+;Ro+[2BeC3Rm:,@/n%"kCjRZ>R-pk3!%pTR+m@F!rD\8iHY"&+5UF"`AkTV18dp]anKEp/.X)qc:h@.9d&,G"fo<;'7OE+C2`TS#lG87TD@Z`q[\a?9SVU4bch^>2W+km"NcB8%G%,JqOJ.$c@\YW82GnqaDZRl+WjZM!.GE&57O3[?Z5MN&(.R@N1PQJ8>uRllj^4S!A=*DlV6Wqqf2<e;UN(D.``3UB1gol'^"'hClt@'=aaJ;s(n=GHjK^![Pl#W=$+np2gQ$VDr%35J"DQ]0636.8$s@Yk4d.2RBXi'8'kkh*pfATc<1"%$JCAjs7Q;@KBpS.nD.7S7lo$i=T*nR3i28S=q-.flW0%p7\A_t%cX6*YBT7a=5[&ZR%XRSkndEf9<(FEG3-CX9(G:2h8j$eR0W]Z09[fb>h3M7Rdm<p]NO$H7UePJ6p]jiX<,FjghNRk$HqqUG>d$a(!;_n&(t*#MnafhigDp,e4b2;#$)6eBa;ob'5WBMK_F9+dO;/c.h[%Q`BDu8,F,:[&CmMT/F5]8WZ?^q:5$!^Vd8-BUS@%<]&`uqX`2DYUdC3@GrH!5o3+tkr4WD:W"`E8MRldKmT8Y>)kfllg[]tl0qu3"1>AMWVOCM,k&nZsFY@Hg[$J-cNZS0+>AG^rqY+5O$rg6`""n-?BA'.@oSf:qHKtcPf'Pl>UfFtC]>0T4[>@KiG7\-P8Y,MVdcLM&:S-_:M*0bb%'3SQd359X)'=60d-U/G.MiU.KuHV:>\NXLRIu!B~>endstream
|
||||
endobj
|
||||
43 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2151
|
||||
>>
|
||||
stream
|
||||
Gatm=>>s9;&:Vs/fZ1?4I!"oO0l;L+)-SGam<Qd2i[@*pD5Fr\VSE,3HoE<9W.u7p]X%7[m\`i26%Rr)/UrO1^q0dJF%1^9=DWNAQ48`[3*f4,9e8nMi)6eX:O'[j@Y&e*l#r6Ja$/UN@ri,Ti)n3E>[Y:bD:ccK&iOYU3%4c6n'V@AhfoDX[s!:?BoU)O/N7&<"eTe8*+9Gso]fo"R%IYjJ""91;n/5K3G8-Vl1FRVIdmUX5@F#i*luR`nF/R0Xh_9ViU@q2=Muu:24S9(.e4m0kZ3BUQAg+Y?7-]"Q:\#CeFR5TNpF:sXMD5,io4;U+(u<$dKP9X(86cOeA8ps;<m*FY)+G/dqs_?j7b+&SA>ltU!3'I#9OBRD_fS'gu]NR74QZC0W06nRV>Si0%335!1]\(Fak<(9"Dt^4+IN@Q`QjU"0qdEE0sEQb,IUY;nAfCl+Z?F$PKH7ct2l44NFpY(TA.h:\1[NPsC`qK8gHFNg6$(+p`FCIJN^0r-E7JEXAAP?,:R(OXN/d2p4,^0`0h]^-t(B529S=IjY[8bGfRM@HqiqfB7:ZA$Fe)o)6AE7KbXTa!YbSHqFI4f^SD!_s&qgRP1POS1jQZHn"o%eBIk$VO%jk3ml\]_ueN$SG>W)=NaAb[fYIR6n4jc+]kq%:rffV%s")>6,H6I5L!+R9s:tMB!=pn:=UGKXBW,pdM&dI<,P,/^Bt9ORE?@qMb'a:/eM)i[.ubK.b.@+"Yu;$@$$c-if?Q&^d%mT(r9e7o3&oe((]397'5@_,:.PMi%A_*4KDM]*FLt:@\$mB+)"X,LH)t2%W@[$dqbO8rK1s&De.)E.n6C2Yj\X/a:pj#IUqfSk9Eko\5T2lGHBU4RM>P4nSK0;^;tNR[@=!raTsgMTKCAuiu(=g;;Gf@MX0JdBd^e>O-sG.b]o+gf0\.7]QSa)YCb'm_#DD474t#k^V[=0@Mu\SXd/'neY6h"&'^.TF-NPB(O=8qW>4p<VR>U9C'f)_*gg`h[JE+MH02BlFNirLV.2Z)dnc?YQ2tj\9Q*a*-uL(YHQWdDOg1_dF+WG6EO$,.6:Rnp-s)PfWal=-=:<nLorNk)NDPu`40b!s$ki(L",i'"Q2>RUD^@/a;!/`hNJ)UajL7V:GEL,B%0$4>R"<rbMnDRieeV5b,FKEP?ATrGT!;J&,C(Y5!d_P'16#;"%H_`!S0N%5CU\/6W=h1],T"gYWo=HK?=[M`.Q3RZUW_*pVFO,R#R5@f@/JI_*X<QTlUKj9N8&Eu:h9(_8rL(,SZ,5G.;d7'-I20G5Fgr'X5i--?[Xc3VcP1_7?jj:$>^))PM<IZY08h?;l@8:FN;[H:nQV)U/33$k"sE[g`kShB[R3A<FSs>MCfQ,P(a_DP^6j1P`;WKe9/f0>*Dpj`?!>?YarYR'E_S#@V52D;-Oa>4qHA-CR+:/U/iaA8<)f_FsQ-+K<n,3=>_<rdrNWF@9#*5:R<k9-mQ5/^?`j+7l.bJ]nF?LZ!l$J[L;F(eAq0F*?!<NGkuW18oa(0ch7[M#beEgQRAK5W4:G"CTABW'S-JoVO[u-RUf.N2!tsK=%Uml!)H!cAUC#bUWdHK=m'Op_EKQ2WlgN-.,6N#h8)#*;q$-/'mV^pm?#nrHr%VRRs@*[PBQo=JCii*kGi]2Vkjd+Lf_)rUb\/b)%Y;(Q^$+#ZZd\]-V;WS'PEbkVTg'F@$-78HIUnXQ/tONSsh18Sq;+7SO&R"]GTke:2HsWngpubJc5*>Ddf4DPih*"9&2/XbfnVV*`@n?f<M;DE`EkJTjFQ>0cbU7\s'1!Xa_BuU+N:`iPp3Z7_6comZFt.o:`-BHIk#-YC_gopC[?%443akfq9"L&%C=t@km@6D>6t=H"HsNh[C\;Ue8(DnsVoX*g&d1C(b7/ZQ:C_./:"-Dj@_1035qYb=B'%^\TY2[n?G>Gr4(;U8<(c-cQcJfBG(AYq)23TuK/s()\N5Usq3k\@OK-/0:A?I[.CG'Ri_,caPo'a1gpQ4==?!"lT6)@bG_P?L5YeD3en)X4[-#[P<l[CXVP$lZ-AUF"u<+@C%\VN;$#Cfp_-O6p$UCg$>&"ZS`>=@)?McEd6JnXif#iHbeQX)]aCmgEeS6\U!VEga6%PNO`m_p+PM<9`>:3i[kr~>endstream
|
||||
endobj
|
||||
44 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2180
|
||||
>>
|
||||
stream
|
||||
GauHL968iG&AJ$Cm&b"M7L4P&j/=e0`gO>[CpbWr+;0jbOG![uEi6[d$]6UjZ>@(?N"V&q!nbK[\)/f/_8?e#r['Y@LDdc)7"I+\/V3c:LY5b=R%=18,LiK.5_7;'j"UYQ@eTuWeQIH],Kbl43<p-(_Enh7M[9%Z+(m!(rcX$ciWj*T.MtQK6VN)4!WG\q>=:`.R)Ka``_!MS/;M?lfBI,Jl2(*g$6LqZDE?sNiPX3b+1(4`?X`Xeb^O=dE[Q*\HQG[&%fL?jqi<)'F&c0D0$;>ZC<cugl,:B-Yn2@C`DWV68@6T1\k"B(.,[AM^^n*Y^I*CASPQo7);HS\'qo<3JZb_(`!(_`,s!jGS_;^='%*6-J9r[hTb."Wm/K`3EmB)NJ.cd92"R*e55eV&cpadI@F,!:T<?E5(gT[XaFkX&BAF-#\:K9&U8R.XL_@jEZtiF3QA1CqOf!%&Ui'VVg2hok,#Nm;9(KkZ)enu<$(J8\H-SD#7!aG5K\O\^hllJKZ#Me9r/<o2_Du8gibleurMK?%nHMo9Gec=Hc8lo#B]C(Af^XL$TA)?gi!1unU,^"U#'Z.%&&%+U5/Mi2pHP0G4:]1DD-EM-hW%"=k(rPtTM)^0JE`(TX:fp<cAY9jk.IoSNPn#-G'9`>IY5mY_&d[1`kt0tciso=^097U64a/._l_\69Jk\Ln*m^'3d3ZiQuKlH9P5eJDKVB>Blh!5H\*[2KnN;+jVtQ>-DH=E<pO>2QH>5kkMD6oWFAOJ`WHE-n.-tfgjH+>1H>lBa'."Peh#lKB0,Fe@msdWMH2?.eXJu2nJQAeI]F=WLkm;U/aeED$#/JtXjS_*l'4Z:$QH\C%G76F_ubl^*RZR#4<GVo5;n7C>ANHTR`X(YW-+r:g<*1b,d4OHT<I?[AWRE;@Zm3[AMTOs'$gOk*Zesa^Xoo1LY&.c`^t8X\I+bikOnL&B?5tNl/VXThPuPfV$-s\>7IA8;ONsd,aPme%22ZJEN?L$+19=AeB/8pR.Gj+2(k)l::)H\7c:V[\@.g.i0Pd>^OSB^e2^&NPD^=uK![c&Ub6FN*OXV6K48&&lWcb&(Dpd5eJ^A?-,G$2Xg)QDHK`tLKgjp#c44(iKdo_%M9Oe-B:Tk?o>N.dH\jpifcb9AaPFsAY%6#YoY6X`!o!jK^C%S(4>O1uL8Q*-VpT]B<sJ#_`r!6F<-EoP>s$u=Y7\/NnWM'^3ohJ1dFBO&r4p)uqJjjpPJablZ6V-A!9&ZtE>a<,Q<F0I.E>kF:lB[1^;#;sbjM=NO$:q;j@Y$R5&>ZZK\hi/kfV)O)a61E>,hX.<?X7mQNQ5H%Z/a%b2@i;qDGLPa3r,b.J#C-5"PXu.kjJu'g+q)kNbgPBHp-hSd"bn<GPOnV4bZZg7"c$GccW"5dE(DMiU+[$sOp3gf;4F%nRI^D7,W..Z:%/dH-ZU'5>^9(Mg9q_T3s6nRCU]0tFoO?l^ih,17+gH(e7cdE[]1aprubMBi)k$lDtn1L8d#U;!JqL+:XG$^lC_n!ASrK-QT?J+DK2oY$:bh"a]A]0E<l0!NW1`GY.tA*QB1E`_[`]jQ/-$&'Aj!cfQ(4eLLmaONo9pOAP;lhC<Y!h\1K3Fo.!A$X>Z]BuKJ.jX"FLXWi]?/^E5gfkE:p6Y#rPrUnR2>^fA:!LKBRIa*N,R.l!CHFF$FX>4tTbtg7N=o$]lLdk^i10q)*u);1WKMV(USaDVDpDJ#4;b`d[?C\=AC==SfE,YJk94&?3^H*E/f$q1<#7(*WWI3H@)n(J]>6AB#"ZC,mAOp!$=uqg/$F&(9r=^<gu=&#'8OJF!LF6_%d5TiKb(9#B&O:RO6l</=-!a%l`SV^X3(R+4jL)J$V%:(hP!M.'5?RC#@K>mFFN44kGQ_g8R/tUaW]Hj<S<X4Z#qkI%7N.5VRi8;G$#TkqWk$!_rP$Ko0L44@3(JCQ)jFjK+0&AUu#.#"0TFNB%>^J7b<2\>co[PT6LgFh,W^ZR^>g>\:-[G2lX.'4,tihYYis-k4&iYs8.b.Oj>KQ,4\)s`'E=a:MrCh&K`(j2dQ7KHH-KN9=4]S=srf_IeH5E7oQ#4aLs#AMo6`YYJWgR2s`sqg(E;J'=h1M,EWr=+r]p+E7g!M+%pR['u]W#4e2?(o3a]rhA4O7g^aTL:>b'Xann@Os42SUlp(=N\'G~>endstream
|
||||
endobj
|
||||
45 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1927
|
||||
>>
|
||||
stream
|
||||
Gatm<9lo&I&A@sBm*X(f/AZ8uVDrV9*P4p[V7l'oOcYNh,T)&R1AUTk!3%V!:6gdX@nS"3i4XR9G#r;:j+k1[78<E#:4HM'@KF_T`$-k/q$g']B:mI3k-I0YjKZo>o74Ir[hhT=cPep9Z-\E(0Vg[h]c`:J$8O8nOpEQ@X^7Q,;uC%24!L?ASre<@GQ=nP&PJX=8KQrOZ68ep%eH.7hq^ugPH3Bmm=a=il$foD_Z4UmVgm#?2o!c<,L'ST)d*@uD]e,0Q`!],\G[@HoX/?n78qZLhCZU3X_"2>&HjOgWb*PMd1haFHY7VLS7G2^?CjQaTAVTG)n(T[^C*LJGs7/8GROdC`E8!&&fXIH1YDtR`1M@\A+5-?YeWol(iLu)ItT_<0FAOLcQ>CWILm%Y4r?_ol%Epg!.^^.e38kOUgY1[;,/'P40`gm%_e9X$Ra#_a$Q(4_K!WUU4(iZkrfYno3`2i4OQ4\Z37.,e3D6b]i0?"Z<;!pDnuTPl#WXO:%.7hPPHD5m-V2RQH>WL,4nh3L,5:Hjk]S.o%GG9li>c6_*sdTn%`N,)/K44gS",s;>Co_!6TM(8*+NDP2+6d4Mur3mU+`k;;L",LIurL4Gt2)hiXPY<StlT8jSY:]eg?.Oh.@`J"B4Im,JA%Jm!Csm"9m+Ke,p8DfEX!*6`!8+MqBkMFp;fLLt%h:+X2\49eFm*Q/'dr.prZ50":(!uMlmAHYXX)!2!A_En8"9]rmjd&I^`2mQT8)E.'C"QD,J,hRaYU@5UcAT%q4J,sSrBM+t4%G/*\H?;BBOf5830sUge<bb?\@'/^F)fJ*lGNZ\mksT%3hujEo2l>Up]uC.5Z?[/GFbBX-.V41[Z.nt3.OnDl\A8XCLqp:VA0Tk;q-:l&QW@d6X!I_%b_jm9A82;ibO[h(LT"5n_;)&("'EZKFuF`-d)nVT=gM(4QWA!;(/lfS^/JX"2D:>R[eM'3[^)rX8T6)NfJ+c6`33CIC2$LH"[,/GJ=_"^1i[&[gU-'XY)gp\Nr>/U#5abEESf0A2]RC'"Xau,Y"#a?!IWcd5P5bmeoG7[WJt#ig`@kh6/9%eMjeH8V?St:Y7<spO''1/mFJkumUW-X7*S:ClA(R-Cl^Gk0=QFd<7p:e_.Q?L50de<(7e.*44+M6@H;/\H`%Sm/1!&N*.]G/:8_7]"+'<c2FS4Z:lfcGaBW,#W9eJfKNc5TJf^^*5@R[QR%a?`[\?)]Pda7"O2&km.^?EdFL$':.sf[TU1q-eUEUGUZq>i5"Y"L@hVcj7$@D-:3]>dU:<-F.f1-.fZBGTMC<.@#VqQ9'lh=1S`\@![f]U[jgZOY_[_6W!@sNmqcTV"2\)7GR_r81QM:etVo)S$@.%r.1nNoRK99`)S&os(.Mc*eV_96IkEjmi0rcR7'>;[RHceW[9q;FS[KO/($S8:7?TBfcn?4.lub0:>GL[m(F1u.Zh-u];WJ%lcciP>)DaW=!-I+\i"ZI"u.=c+A#'H2aBW]<]$(r.MLh@'c.W0Mcm1p>KO_AX!W#;2[.rqjJm1ML'\o84>nWRTtI5o(`Nf^_(A"aV#Pa+t=<0/H=OM<Jb%Zm[V7,ueA@1><T&L^[S*ejB"3W>]*r*o:U23;Imm?M%^pY1$"!Elj50H;G'nK0"`_Eb[d^JlhLnKL=^?@sI2Z9H;V=E)t#/a,g;1@(s"r+m2<AAgDO.qB.kBhT_?UkU4/I[[Y,c:FH@-5]):T$aabTU,tFCe[4=C5seC(<'bmr7U&=?fdme-9([[OZ3euh!H/*b3IM</s(.M2VU5VNJk12ugG%GN0\]r<XuZ0RR9GkV-:EB67&(Zt\Xc"GJ;8],%hnp2?\?pY'j+LM:IhWufr8-!20tDNJl'e/:"=m5Q]D#T1Y+^`??5iFK^DL'P5BAC5nM\BZ.aS(&ErIFI;uB2~>endstream
|
||||
endobj
|
||||
46 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2037
|
||||
>>
|
||||
stream
|
||||
Gatm;9lJcG&A@7.b[l?AO`EmJPc2:+D-F;;Rr%EX!fL;sR<D\'Ua+Q1d9jYL[/?1ID2JdS`3_i?BBHmQ1ZXeZLSqn09D!$oJ%Xj\"c[05-'L1"T_%]qm0VejY6n$`lR]4B0i>mpeBLcO?Ih5O9'/.58hj2CJqRaH/0oC@RQWZ^;>agG3^Spg-WI/a%_u",pnA*r<2/BJ7F%'MED*32(SgNajlQBK,C4?)+%Fk-0!KSIrbaj0pmoEsB`)Bp'I1-FHsUMfQ*NM1s!#snf(rsNo7#A\j`Ra$gsptYKdc22n2!#2^36A63/^e:cp^^2.V+q^pai].gRMgOAE8nb>a9%p##`EcM&2&:9-gjr-$/RjA1C%;KCj-!8u8bJrdV:\J"=bF&80\9Bor_0=oI.]Edh^a+5d.YF3AM>`YR"8'M;E5okk:@Q0=;arN/.JF2i:(O53CJngYZ0E7/)9de:XH$uqM(1L5N7Fo,[?*LrQqJh?gGZKRfL8=sqAki:t>f6NZh7\3ji^3auo=78ZZ?66ZII7L)Fg"m<8T<[S(ajpQk#*_c2Npto?)Gk<C()><0deM8;LO8E$`M%^O<B?=cp[`9q6>dtNi$hQ1`^i*gR4`QBj>3?u\"q+?d%?Go7:VWq(:r)De?1Cr[KG`9n2Ie!;aY\@"1C3SlV-BVC:WW6ePL1e6%Gr2=9k^s8S<A$=GQo'3-UV/d\3P/3,Gr94Pj4_@HRFC"`IXAB$jaDD9iKq1bm77_6OQ1R=rkmA>obBCYQ.eK7^ihYDOeVm;PF\-.5?NPe.C6-biCBgj*(l8Rl8_9<&r?fE7rSR>O8s^(`VWgO*s@C9?M7*%RE7cpFfZe]C3H2tYE0ngD0q^i,6A'or\%+]+nr+E=5hcK/(<@!$VT;gd8S6'_rJ97MIpm%ehE1(<^>f;^+J)tN&oX/SdS>GWK)&]TlLcjbNX"@!EIdX2LRdKf8R@/ta!.#1sXe^EeE&b,R=AO3,)9aL.)G&rP03P9B#'l^S/H-XlaXmKjZ%5`._pc;PMloH>.[-Xp0=ga%'C8d1`F=r):^:/1MEifB!VE<r&Q?=5#&%XX\HUN45hbQJ:D@$@=V9T"$?gSk_h(&j:6q_$4Qf5Oc8sd.!J6i4p*ds+WMEqXZbKKf&kg`]V3Su'2H?H.r+Q1IeaJjudNP)5hRX\Y.kRrXlr@]H19U6<27,0Do2(iu-Fe(r/0(OfUr7hmg-W&u/,%20"ciZI%($Z+]3HtPmVr<XS/RU:_cd^/^8]ON0rPHRW%V;0a1K6W!oiEQ8/2q-k&iK)MaqdAs4qA0#/LC1e6$>FteHZT'.%O]_./&4ns4ep"o1-\l6Kg7\SWq"S[Tg>2g\P##J.XO%*%EWo>??$BZ)7-<cO[\n2RWoKBEFqZe((mJGTO+a7GcgQrYVqNE%S3)>@[M$#keeE7On(EfXr7kpea<!,Q8Np^B3/Up&SLho$QC2aJYk"$]O<f+Z93RF_:CN#Eqh7Jid"R%sNesG>G@tpcleqYj2R/C0Xtm!%f7$&jA^2ZhFr#B%lX_d(+Te8je\;7g\'l/FI4+_3:^`#EKG1/)#3QR-X&-lKl^2pn8S&/iM(^Ve4IL0q&9\kMd#";Z+q(`U9g,S'H9_@]ebs_DafND@DVO)^5?'&rRm1"R_fR-4Y2OjkoJn4,_!Z=Bi7.8r8jOqS\Vs.lL+PG6Y`/^U4SHR"I_t@"bH#`8hbl5CF):,(I7gCSCR6VtD4uFrXQ[<>W>PgBVgQU"(BRSZKtYogpVMRua&?ghpj^_hE+LkF&$]!&IPX0Ri#9#Jd!=`?e\^qooQ;O1O.%/o;S_E+U,%1qkk^dQ3]]-:\S37MIj5Gu`]_HjJNQ`39:B`jKou(#<qFr8h.FO;PNJ2nn1%XP.O$rQ3Uc!&Cg19HI!DOpN^_FD3j+anJXYG?=f$^olK3S`n&llEk__6AJB6BfJXp;o]))Mm\1HW10IrcCgPIkg0E0Fm\8lo/\D@E9u_5PUrY^Aig[dL^7?.8+MX]6Esg3G;qeJ!a!GOktZs;=R"m2@bckd~>endstream
|
||||
endobj
|
||||
xref
|
||||
0 47
|
||||
0000000000 65535 f
|
||||
0000000061 00000 n
|
||||
0000000123 00000 n
|
||||
0000000230 00000 n
|
||||
0000000342 00000 n
|
||||
0000000547 00000 n
|
||||
0000000752 00000 n
|
||||
0000000871 00000 n
|
||||
0000001076 00000 n
|
||||
0000001281 00000 n
|
||||
0000001486 00000 n
|
||||
0000001692 00000 n
|
||||
0000001898 00000 n
|
||||
0000002104 00000 n
|
||||
0000002310 00000 n
|
||||
0000002516 00000 n
|
||||
0000002722 00000 n
|
||||
0000002928 00000 n
|
||||
0000003134 00000 n
|
||||
0000003340 00000 n
|
||||
0000003546 00000 n
|
||||
0000003752 00000 n
|
||||
0000003958 00000 n
|
||||
0000004164 00000 n
|
||||
0000004280 00000 n
|
||||
0000004486 00000 n
|
||||
0000004556 00000 n
|
||||
0000004837 00000 n
|
||||
0000005023 00000 n
|
||||
0000006282 00000 n
|
||||
0000007680 00000 n
|
||||
0000010258 00000 n
|
||||
0000012431 00000 n
|
||||
0000014575 00000 n
|
||||
0000016764 00000 n
|
||||
0000018722 00000 n
|
||||
0000021079 00000 n
|
||||
0000023292 00000 n
|
||||
0000024714 00000 n
|
||||
0000027170 00000 n
|
||||
0000029111 00000 n
|
||||
0000031488 00000 n
|
||||
0000033899 00000 n
|
||||
0000035713 00000 n
|
||||
0000037956 00000 n
|
||||
0000040228 00000 n
|
||||
0000042247 00000 n
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<e9856c676444c494d18feb53bf5d2892><e9856c676444c494d18feb53bf5d2892>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 26 0 R
|
||||
/Root 25 0 R
|
||||
/Size 47
|
||||
>>
|
||||
startxref
|
||||
44376
|
||||
%%EOF
|
||||
1799
docs/estrategia/plataforma_saude_mental_v2.html
Normal file
1799
docs/estrategia/plataforma_saude_mental_v2.html
Normal file
File diff suppressed because it is too large
Load Diff
710
docs/estrategia/plataforma_saude_mental_v2.pdf
Normal file
710
docs/estrategia/plataforma_saude_mental_v2.pdf
Normal file
@@ -0,0 +1,710 @@
|
||||
%PDF-1.4
|
||||
%“Œ‹ž ReportLab Generated PDF document (opensource)
|
||||
1 0 obj
|
||||
<<
|
||||
/F1 2 0 R /F2 3 0 R /F3 6 0 R /F4 10 0 R /F5 13 0 R /F6 15 0 R
|
||||
/F7 22 0 R
|
||||
>>
|
||||
endobj
|
||||
2 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
3 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
4 0 obj
|
||||
<<
|
||||
/Contents 40 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 39 0 R /Resources <<
|
||||
/ExtGState <<
|
||||
/gRLs0 <<
|
||||
/ca .3
|
||||
>> /gRLs1 <<
|
||||
/ca .2925
|
||||
>> /gRLs10 <<
|
||||
/ca .225
|
||||
>> /gRLs11 <<
|
||||
/ca .2175
|
||||
>> /gRLs12 <<
|
||||
/ca .21
|
||||
>> /gRLs13 <<
|
||||
/ca .2025
|
||||
>>
|
||||
/gRLs14 <<
|
||||
/ca .195
|
||||
>> /gRLs15 <<
|
||||
/ca .1875
|
||||
>> /gRLs16 <<
|
||||
/ca .18
|
||||
>> /gRLs17 <<
|
||||
/ca .1725
|
||||
>> /gRLs18 <<
|
||||
/ca .165
|
||||
>> /gRLs19 <<
|
||||
/ca .1575
|
||||
>>
|
||||
/gRLs2 <<
|
||||
/ca .285
|
||||
>> /gRLs20 <<
|
||||
/ca .15
|
||||
>> /gRLs21 <<
|
||||
/ca .1425
|
||||
>> /gRLs22 <<
|
||||
/ca .135
|
||||
>> /gRLs23 <<
|
||||
/ca .1275
|
||||
>> /gRLs24 <<
|
||||
/ca .12
|
||||
>>
|
||||
/gRLs25 <<
|
||||
/ca .1125
|
||||
>> /gRLs26 <<
|
||||
/ca .105
|
||||
>> /gRLs27 <<
|
||||
/ca .0975
|
||||
>> /gRLs28 <<
|
||||
/ca .09
|
||||
>> /gRLs29 <<
|
||||
/ca .0825
|
||||
>> /gRLs3 <<
|
||||
/ca .2775
|
||||
>>
|
||||
/gRLs30 <<
|
||||
/ca .075
|
||||
>> /gRLs31 <<
|
||||
/ca .0675
|
||||
>> /gRLs32 <<
|
||||
/ca .06
|
||||
>> /gRLs33 <<
|
||||
/ca .0525
|
||||
>> /gRLs34 <<
|
||||
/ca .045
|
||||
>> /gRLs35 <<
|
||||
/ca .0375
|
||||
>>
|
||||
/gRLs36 <<
|
||||
/ca .03
|
||||
>> /gRLs37 <<
|
||||
/ca .0225
|
||||
>> /gRLs38 <<
|
||||
/ca .015
|
||||
>> /gRLs39 <<
|
||||
/ca .0075
|
||||
>> /gRLs4 <<
|
||||
/ca .27
|
||||
>> /gRLs40 <<
|
||||
/ca 1
|
||||
>>
|
||||
/gRLs5 <<
|
||||
/ca .2625
|
||||
>> /gRLs6 <<
|
||||
/ca .255
|
||||
>> /gRLs7 <<
|
||||
/ca .2475
|
||||
>> /gRLs8 <<
|
||||
/ca .24
|
||||
>> /gRLs9 <<
|
||||
/ca .2325
|
||||
>>
|
||||
>> /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
5 0 obj
|
||||
<<
|
||||
/Contents 41 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 39 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
6 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica-Oblique /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
7 0 obj
|
||||
<<
|
||||
/Contents 42 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 39 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
8 0 obj
|
||||
<<
|
||||
/Contents 43 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 39 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
9 0 obj
|
||||
<<
|
||||
/Contents 44 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 39 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
10 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica-BoldOblique /Encoding /WinAnsiEncoding /Name /F4 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
11 0 obj
|
||||
<<
|
||||
/Contents 45 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 39 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
12 0 obj
|
||||
<<
|
||||
/Contents 46 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 39 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
13 0 obj
|
||||
<<
|
||||
/BaseFont /Symbol /Name /F5 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
14 0 obj
|
||||
<<
|
||||
/Contents 47 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 39 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
15 0 obj
|
||||
<<
|
||||
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F6 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
16 0 obj
|
||||
<<
|
||||
/Contents 48 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 39 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
17 0 obj
|
||||
<<
|
||||
/Contents 49 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 39 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
18 0 obj
|
||||
<<
|
||||
/Contents 50 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 39 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
19 0 obj
|
||||
<<
|
||||
/Contents 51 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 39 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
20 0 obj
|
||||
<<
|
||||
/Contents 52 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 39 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
21 0 obj
|
||||
<<
|
||||
/Contents 53 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 39 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
22 0 obj
|
||||
<<
|
||||
/BaseFont /Courier-Bold /Encoding /WinAnsiEncoding /Name /F7 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
23 0 obj
|
||||
<<
|
||||
/Contents 54 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 39 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
24 0 obj
|
||||
<<
|
||||
/Contents 55 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 39 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
25 0 obj
|
||||
<<
|
||||
/Contents 56 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 39 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
26 0 obj
|
||||
<<
|
||||
/Contents 57 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 39 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
27 0 obj
|
||||
<<
|
||||
/Contents 58 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 39 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
28 0 obj
|
||||
<<
|
||||
/Contents 59 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 39 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
29 0 obj
|
||||
<<
|
||||
/Contents 60 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 39 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
30 0 obj
|
||||
<<
|
||||
/Contents 61 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 39 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
31 0 obj
|
||||
<<
|
||||
/Contents 62 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 39 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
32 0 obj
|
||||
<<
|
||||
/Contents 63 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 39 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
33 0 obj
|
||||
<<
|
||||
/Contents 64 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 39 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
34 0 obj
|
||||
<<
|
||||
/Contents 65 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 39 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
35 0 obj
|
||||
<<
|
||||
/Contents 66 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 39 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
36 0 obj
|
||||
<<
|
||||
/Contents 67 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 39 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
37 0 obj
|
||||
<<
|
||||
/PageMode /UseNone /Pages 39 0 R /Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
38 0 obj
|
||||
<<
|
||||
/Author (Uso interno restrito) /CreationDate (D:20260322013413+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260322013413+00'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (Ecossistema de Sa\372de Mental Brasileira) /Title (Plataforma de Sa\372de Mental \204 Documento Estrat\351gico v2.0) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
39 0 obj
|
||||
<<
|
||||
/Count 28 /Kids [ 4 0 R 5 0 R 7 0 R 8 0 R 9 0 R 11 0 R 12 0 R 14 0 R 16 0 R 17 0 R
|
||||
18 0 R 19 0 R 20 0 R 21 0 R 23 0 R 24 0 R 25 0 R 26 0 R 27 0 R 28 0 R
|
||||
29 0 R 30 0 R 31 0 R 32 0 R 33 0 R 34 0 R 35 0 R 36 0 R ] /Type /Pages
|
||||
>>
|
||||
endobj
|
||||
40 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1874
|
||||
>>
|
||||
stream
|
||||
Gb!So95iQS&AIa;bb[QC1`PlLP=NTXK+q(NfM$Y^7A1SfJKBgs\,H,nI?7WiEh!1s%kGOEGJBR%R;[ku!Bf@<dLZ)%Rh``>)I?`hi!B!gi0$[104A$#h[3C6CJb/naH`oY+`->\g2!]\0f2LTQ(1u]=kL1Pq0Bf$aXbV-[PV"l@"Jc=$Ed_P9YfO_HUY]_iY$-B>(h/U!d"G7#cIf$N<"t>ES"\k77`C])^.:pO9`j4O!,Aa3JL4\bR%oDDoY[E`0^iCW=WPl6mP-YfoV=*3G(WJ2WU57);1*]2H43SNS.-j%5h`b75iY_%:+-I4G';&(MmYI/I(h+!9aHV%mVuPWWV1r,XL4iLXZl5$"SgQ4[:Vn'$(N/8.Hf25\::ieCc'b'Bk7.KF'B/66ofl+W2J46Oqe2#odp+L.p6]6T/JlL)R@k&iS7$%@+/kL289%T((,>-d[qaS]8WG?mKRP+WEIN_[P3P#odKtL.U#O6T.WTL)mR.'/o3=%@aS1LMYVa#3-Jd!bRZibV//2Qb*mm]4,'=ngDO/.4\NrU#LZ(Jjcl>A^Do'[UOjVHsHDuVPkarL+@829@_LVRt=*2%BC0Mkk.aq;juP[DW-&k'`H>U*2`Ql>hZqe7Lo"E*Q6_R'Y-:RF*CIBm\%k]6'Jp52KoD,g`>JOQSR7G1-GGCL1al<nEe=9Sp`c>ZS-!:p(\%_^80`@f//kC9nV-N1mi\cKA"j5R!h\]g"/'JI8,@If//kC9nV-N1a;gc7V#lTM!SmqOaTqKZEIt1p,.iN^8;Lqf/+]KRRUghe!-Rn@'Xf3jJo:R4T%-&e2ON<8!qYmG3F")?&p(bDZu2MQ/(cQdIe)cVKc[F=Zms:AM<PT?m9bhC:]Wt]'H!Ldb4/jL/+.qLO/&D2Q^oM7+\T`9?ji>q\&0UG]nOYp@7PB?$un*S,.G/h[7Y9^H&oXMi[>P-YIA#hG#VPL&*=Lcf)i>G?U34Vl_i<pYA[^'6aX='Bcc=KQK^#2j1o$o]67.#`e\Pm2V_2^RWf;MlZ0Q'0)V11S('N)2:I43-[6i'`u^Zi4Ma2Odo!I4m@jPnd/C9UTP_`Y#n=odr!$GE.o(cc5!54a3Y,"pAnm>?diVngF[UGFu=naZasMPq[M?,[PO;[]D\jn@KXmG<[b@^1pc(ccX-sTX&>3,3oAdW1c#b';.[)j0@gZ?],@tnd/F^@p[*-I:TNHlSsJ[kU6q]82c!9rbp1'J9;[EJ+B;%]0;$to@L`4fVe&NI7'e7:(AFtm1pj+Uk0G>ZOd>_b+H-Fu0lF,.Q-f:V"u3]GP._2p^C3.pkq:.S+5J9rY=qSGr]++BMh=R5Phqu9^;-Ae@9rfZY4"h<1RAO68/YtA*Y'lI4juS!i<@Ba?>"/_1p6"EQ*(_Q8Xj+,RECkf_sfecBIOHfY]%&>8>_.ok(H6AnVs]IHH8<E\lNU([`Z'r;tP@AIP?7*mFuD^I/VH1G9MX$Pt@E(LlB3*ih<^R4.MhJqUDkB[)&!N];P,Nfk12.ES%@_`GF4sRg=t*SC5dqRTo$K@]lBbhWM'(Mou)Z&,3D+ZX!rgEPlm:G9T&Q)qFs_1X_\81r%r7p@E-Jo7=cto_'UE,C<]PndjGImsq$=0*NdXAMb5s<,).$87LfOo_XoT$]bXYn^T/,(EV@u(6njIJjH$L_G_IYDb6YPSKgf2^I[C<n:08R'28U+9!_&c]V5^*oa7@"8l;F<E@=0uCq?a+RtGaooX[^g-PZhk*qYbi<X\K2P>;m^OCOXVAOrn#<euZD(`f%%'Mm.G`ilu?ma?Sieu/GUB]m!snB7]PpRE=31i\BCYX1%Lf8ir:p#B2a;VWs(i87+]g[Y.jDV48tCn*?770F>)!qoW)j8~>endstream
|
||||
endobj
|
||||
41 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1546
|
||||
>>
|
||||
stream
|
||||
GauaA?Z4p`&A[3!'_Bs_WF#UaP=Gtel3[k'Blss!jFZ>gKt'iD:YJUZG(9S)Ks#NW6Nmh5p5J/n[V$p5JB]1853"N99aKWo#nRI&&dUuI_BB?)c>*YniVSiA,*W?d!(7u;FY_EA"rR0-7mI1Y;(4U="C]G8dmCnACBkFnZ[`,:J\;%/H%7Pi-i8@aI7_nZfbYdgp=ZDnXbM;,TXd^=EsVn9$2)(](eO(`L24;f%(fsBG!$dfTg+'1GKq$_(Sef6fH#Eln/9*H]5d3$*YNXDBgeO7+&bQ^hi?UGnReo/;T#.Uo;J085upRY@8<eTT$EYD6=k-LV8!C"58E;e1\oLf4K5u\1X7gRKX\it9Z?_jZ-\SCV91T2N+b%>8pquS4@FQN57<S9$dAX,?JW-ZDCkOsn58(!,(smjO%;`C2='WlX%=AWk:bk_qgJ[+/Jfii&gWRYU!GQ'IbiqP@im.QiL3pj^>2\^;me<X<VpP)31;WsVYB*aQ/7j!acT6us-`WUNck9B6ieWc=TU#0KLcWNG<73g?p7)GJMErS4$,P75+",QQQ(1cZ%>M_'g\F&Dr;Q8^3,9>/AnoNg$p>tZHHVk::oJT&TW*Q4_ks-OiO7.`W/M\EV8Y6:[F0iK3.rs5rRW"HIb;V2!'(;5rBdc1r\OWHIT,%I-_u'Dhd;gT9A"k]i03@_Eu-cf?`_m6(Z6[j<4(*!PBeJ@MAs:q(Z-i(#)Bn5/)FjqR_#;?h-V'N4862OUHQtM=:#rU-5*T6([+!=fF8,)!Nf$M[WfmnTB_Q43'p<HT5/l3:1ba-WTI<?8i4gWmL"">3j$S:Z7fkB'o-LNUe'g4H@WDX.W:Y_?O;Nc>sY`.CVu6f1^logLD]OA3,\/9bnJJ:c7$T'1S>11,3,[[ofIWXE@2`,ek>+$'0R:co.;PqiB_38@J4L(:<7??A#uCc!ND$WQIpVm:*KB`c8*B45W#Rh]>JEk[?/<MUJ*`UJ4o=f9>DeV)EFr^4e%pZI12U^bo::aS+!W%AG^?\&T8R<#9_GmP11Ei_C5bPCT//6]g`OOue<05/,o"=kR9$*&L30P"'#ieG0mC.#E#9W[Q2VCulE]F,j5,QST<1jC6XIQ"l6K6*9LjN>#T/d$ZAI+7=15;ng*`h`=To>[EHO30TsLak_%\H=L1@mW\QgfnTSK-g`2jUriJf_>!dQE@A7!nlbmo4YEVsU_*"1kG7E,Kq/p"*HQ)'%QDHelb[H^@Ig_%3`Sh<c^CI"$&_CQf.X*Q,H$&@FUhO5p&I$SRCh9cgOm<UWds?0[tQrOkJt^jrkBF#R>.1ZMqssJPWM)sQq&g%+iaOHm1IY$KAPXY@06P:O,F'u[[1fl-B!c%%s'89]8[.teYDu@:t_=5hid90Y?U2]SF8>F]ps^3=^g[Y3n(,l>$".bdF/6\<t%P&DDD1N(96gA.eR#i3gqV]9p6dP2FJB.A[71'LP]6.JTIJjHdCtJPNBI7[c-X@f=@[8gri\0\$-H#ll7`un*qE4ZE..-[tLG:eChuFi1hBSEAh(FK?d74E`s:~>endstream
|
||||
endobj
|
||||
42 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2890
|
||||
>>
|
||||
stream
|
||||
Gau0DBi!e\')g+Z#`UZ=FV!OmICm8!I7Yr8RKdBt$dpME,t0""eNSlDq=XV/drla2d4f7tL2tt8EP@AJ@7_O>8c(G"M>hWd^OD=kICd!9)2aNYcKO<=dJ"mE7;;%B,c!)g3$[B\ZT+th0?H>=`j)<fMhI]j0h"ubILp"CVVARM6De(Nq'<h901[?sjuotTNr*;b8[(D:*tg$k),*`V_K7OEGNIaUQ3gpp0r<;3M3h[[8[J#pe#Uqn?SB-`dYEm*H$UJ64taQsr9`eAlj[7Ve(*2=%SMjO`:=S?4=oC]L$s^jreEQ'93lZtI;HMbFk8o)(FH?Mg@l8,?WX^KY,g?Vij#f,Xor_IkI3VM6T=CT&-b#f_K4^bc]?l3mqkdrl?#tT#U1fS?+ls$E"]j,pk)8YC#juob20LMg5a7f.Xcaa6Ud4pr+$H8Jb@`C(K`%Dr:pP)Nr'Gr`'KPa&kEVV/-2@4e?_s&HDP8:m8(6Xdg6:f\_SJcRDd1IaJ(YR@<dO""juP>9OT0:i5>BdC_\[YK7\_!h2$\R5'tQf;_-4)JMU)>q<oNsDuAH6+jc&f70n!uL4P$6Z%jj@pj<4r*_,0_2BSOp>n3jCGJ($shKjm([^kYWUj#5+XW@_*`0O>aF34`$fdd&h^&#VTR/X0+<RACUo;97R>MsWrGEkfSElTp)I?@3K#M:)6@(<XI'P<r\@Lc!YO^8O?k\H97_ID/egtO&<k-I%hhsr4PKQTq4^l?:o"%hep-SKP;7/5=5'#AN*6I&;Ups9Eq6c8)lK$8A,2gU0^^fT?VMXn>`(:mG?WAIVIS%;U!e'c3kfC`6WA&Y,^<6ibseL;kSY/a(ZeiXQA;&Y.94hF\F!"O[0Jbl'*D-/uCV55h:kB5Mt(s<!*_I0XN2N($JA)DWm'>^Cm9a_XWQdNH2/W;0CX(0TL5)6EtZ&c=ERcS\B_)F5a0C_L2l1W"AS27[$CS8>CP\IC1WH*V=)V6:)B%:q8<TT3g+SHe=SW$$@ZAFs$$/,Ea-_g-=%M<a'7SIAE_q99V8uo'TNLXnJfMm.P/F,cf6I8\?k%`2fksjV<B,!;iQL5n_E]?n=HOf,LI^^%NOef6U1TRl]2nDg;$T'H0?1hc%L7uLW=l(mhp6A99*$KPCkO1E]M!hpJnj8;JS55qdURLYbl#bN9OLhPUb#&#,lFKCG>+/q9U/)QD\m!Y'b#j].=d2u_"4JK[S4_j['DWD>Rhf2og#Y;?dHsgJG;\1pD$pQc:]JQ#,X$Rb+I!"%E+^\YcbW,_54SLd"Ig^r,ug*-L^7h!p-BNCdXN=aCk+?ce1l4B\7UIo]mcF7,*,(gT&B?s</4[ue0-Cmi*:PH'&4$ZG'_)Me%#gocP=U6LH`nj=o[`q!MXG/8t:qcVhXdN.(/_bpRiF/3i<U$U%`5C]iE>p/NWse-ohISCWA2E%oYbQNG;1]9iG!@=1P%82-^!#OKf9N=+lTF+3qld-4JoDR/ME-(T&CHon)LHhP%fT-1VdT1(-Ii@pta\5fV%(S3r*)"5dG)PhXM0j(5_kICF7NJhtY\n<%3<5)O>EY1N!ALn%RV6'I2b_mB'tF@Z@V9@_G93.[-,*k>B`)>]%E$jZR4)%=8:H3O4)0U>TN4,f_TkZ$OI/Icc0i2@RN%9+p7;\=Tl]G/G0h$d6T=,W<KT@Z5+MH6\Cmj>nFIn]NW3]B&dMMjP@;D#F#Fa\fo;BE1/RS<kFK,VbX\0fIC0)uEiq&P*S$"ShJI2EqcH@JU<f<`YLWGY\9Im(!iDUI[kDr@fe`7JpfR@4J,6r;3>mhZmVAZ]pn-fLp&h]>]t4'-S7n=h=SB6Fdr_++c92UH$$9EU>h8^Q)6i/j:u(G_rY93'5rXl^o7YVSrMKkm=j0[GaoM'(IlcR^C1aok19VWrI=n6]oA$H)Er[F/+@Z;>_/@N2L`r2g^)C)4C-LE<8O<WaMT6eAn;,roRBUCB6?Xs@l!eBQ.CF`[Ln+7BX*qZ5>hqp+$ab>q^24_GlHKHCE,m2'IbELoP!"mOs_IZ<,#cc7if[3H<(P_Q=P,XU5NEEeekJYV0$k]3eBpm@;&(k_.QC9.a-)E$aKenTpKUQRq"IN<=nTZPs<P1*7b:dmLE__"@FZ>+Q7j5fZDj_C5EY6Q@YNs7K=9Bi*cg,J<O)mocqJB(I[*?hr`DtP,8S3Me;,PXd_%]&B$/O;)9MGh%[;0Gu&2[(&4V,)NYjU@L%*tj^G]qJ.*Wq@UI&R!4[Vm+*!$j#RJ04)dkp`n79PG?/NXr/Xor3:0tVO;jhUhlGuoR<7/f&I\;jY0B3$q)rACWu$4]Fu*"Q?K5HUNu&_k,sMq6oT^:"[)[0rW*bFeI1g-S\k%"3HHHtT"8JP<n_MhCp@&])5"+Cl>VgOA+"hp6n\,oe,f_92(%3%04,cfpH4#D&'ND]>fP5Pg;U'E_%R9fVHd;*i5oJdb@\Y:4S4;PA$Ffb3aDj1`Ph>sj?`!Y?5Jfd\(\>`$IR7N1Bu`_`T*=CHY;oh1p4u2pS`7f,?CsHZ,Xg^@B@^6N',n[6FT_miUoBQ]iJMHf5unD[5G,l6]H*8[f&+[V9,4<d&O'=[BQ3p69#C7g'`&,BH71_AYg>-#]Is"Ae*0MM1AYLMR,rZed[&SX]\VarT;rq[>ikG,&gCDcl(bKd&#%b`cV6UKlSt5qessM=tekO"p(].\#n@uJ99+&iofP$\6j<*.*qUh/chTZP^<Lo2a'*/:BTae+o9V'SN46$<aY(8='dOHa!mZ^o[Oe/s-icU`W8i.0h*[Ms'u)!2n&<Gc#0E`ETVjegMJd'`=Rd,me4P/B`8LVe.gm:VAD'h>/oR&c$-dIn'TnHBoRJN]i,(6J5@al:3YfGG;Le+`g')eh9V_RIRisNoR?kCS]L~>endstream
|
||||
endobj
|
||||
43 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2762
|
||||
>>
|
||||
stream
|
||||
Gb!Sl9lo(1&\[L3oPKjF)8Q`q?'U%ObH=RmCTX`3?@3V&6Y[S@6Xp+A/&+9O#65'l;U"e`LI<b!1cWsVY?uK@:l%?Qs1>:frdI4aI3hET\lk5QK:$Q5d+-CoQa7f.nm'0K#UtqDTKlDe3GWRe/&EE);aF!;">WVJ8jj65.)cKelQ=FSObZdW.>SB25X?/%iIA?iVL'clA2k\$%g=uN),8?-_L,u=Sr1p^U'RQV.HM`^Z7s>8Ra/+o>i&`"HM@F+-,$sc&!ro_8E.PAX.O;_hu&1G51@c;UE#Sk"prK!T^m*uTaYHK&+X)]<F>'hZZP<?<ct%/6u:)idO-J"H=<;N.<VsPW]F2S\1:FF_"jfS+*Hm57<)j0#2p\2L,'W1rTYr%VUKBl&O]`13]g:8GS7oBpnK$(o-B8jb22RUmQ)QdVpZsa'Io99i?tp%7c%qL?0n,Pq==uE3ShkhO@EAJ=X8Se'H-D!DaXQl?EXVtZK2SJ@TNRB/GF@?6=_P9)AGc3V_GbKd%Y&k=lcfLT&J;ZkK,2O3%ub*1o.H;KI#5lQC*#M+U^CCjhh$@!rp'ncY!N*-BeR`cLNq-YWE<aM;H!2K54gAj(gOp^FT.7VVXND(D"C]$3/iZdP!\q'1tE74A$4oVinkF$.,tZ!%rWkXu./(PGYgt1\V7Oc90+-Hp,t2_ITUb9ZeL4V&`\8lJob2i^$\"U.@9kYs<Im-BkK-*X<b'iQf%?jJQ7?Hhl+5UECJ[*HS$O,B'>&8>[pR"/cFn0iBW8/El4HS>U4>5AP4nle@E`WDmDHSSi(kT[L3Y;cIMsCHd5Pkun+=/).Mj*W;KMY)RMB2Te!Ck\r\KV4N8UZF1muo7EqEMOC\J7B5VeJ(1i'>I?5W.]oIGAK%ln^:9h>iJF\GQ(H9eg21'-<NSL1^M0HIdk8"u>>f@>=7AqL2$A5G2"qcb?CODA.rAJUNU<EJgVR+@6*1M9JGDK'P$EH+9%(aX!b#!F@IENsKL\b9CZ@e]in_YI?9T#+WKjW.CO<p.ZU(B:o<3G&O@&$5*QT']GIf6JckgMNJ1bmdK-X6h(c4.ZX]_1]T/d%Ep!tf_d2);gk\:K0o^CPWQ@OdGoS1h!lQL`#m&r\R/):F<FX"nb3!\?F\ng&@O/enqd9=#]:FeVq`bd:q-cs&V,W$.4=<YefO.B7/"4c0Mj/?)C@+DrmqL''"I@rJb$'&cRbT8Jf<N-(HYFVf`;>?6a6)_F*G!MPM+<o1*J4-i#_77"[a3V^;F7\.P(srcE;ZJ,%)5&$%j<-m@_GU$MULj_*`t?C;WgCq!.i?g2qD\j2XTk'<S!FM$D:o"F?[>K<'`:kdUUh-a!SK@.k`1RT<+Fa*OFfM!H6JGB`'_m%eYtFdb7!EF!8F7M%kZUi>J's5k0]fBPkqKea^F0Damn03n+-o?C7_B__HImS=j-HE\f/JJ-JHif9Q-0-TYVaVp8_>\oSaIGo#D?`1la#@:G>sJag&p13I^H_>g,M'#Nge[2L"&.,t41AjCts<%"d^t%ZJ[R2aRp!PCDDBJ\hs&TGPYd1iUs5VFE,T0^K=0R')!pMOEg7[0&Kt!e:QhX>/%mhN6bAq*q$Pn*-cBpfL&*_NAKF1=(HPM47CW5\to7#*J/h>jg7^WU5#9.dg^q?Xk4gZ-,n,>(P2E=4#?.13QYWOq-k!-asR8._B-/VU%VtG.mGuf%/E%&("BkLZ#Nik"n1T"ek=Gh`"e<pLZhi%/]pZ=P+/gBbBLd^pEA/<WKHlo2)dk?-lW][HU?es4+nBZi7r;_+HpNq#B>KCFcO@`\\VmE`pQm7+HT=#G>(>6N'?2VYr3h7Tc]f6%>,/?[/2)?#S[K)Nu3taq3F`;Ra8#ZpPU6O'!O?#gk99lnit?ndMt)c%+Y&dk)j=gAu)-FiT]uOC_*RpDdoLDX?t)7YSq'"eBBe\td$f%snt=3+>lQkEqAhH=#cdIj0C"]K[U<1L'12Bl!VTk%ha5K<YAbfA0(U,Nj4,p$G>g=Hpf9QM\%T7F?>-lMnq/(B%UCATU(P)Vgg[iG/t%Dmt9G$B_juhHgtOl!1q>0bQi'7X7gep\9jjaos9?76n%[[IJn=JeVSZkMpJ^JkWJ\7=TJZPR(8/8\&1CpJ%VWj*5G.VUBSNkN?7a>KkO1h5S\*DR[Mb+MOG:aNhK\m#2^CVaW=7NK'q*7[^SsRsq\\3BW!Q9ud]2["a>nrN'%sRT/+OMHM5-Isr5DRES`l,q&LMQj&>9cU"1mY5fpq!3po$8B?<6HcMAr@g$<#j4'2uloQslE;D7"WM<+.(ioaR7%QRg:Ai$`Z4SIES6^>G(%smDNGKNe\g&8Ir)PZ39RS_XaQ)$pbuYV&gP8(%Vk8_lIcWOk1b`NRj*$3$AD9k?.%L"L5mp#'YU>b6jU>AW[[P+^+*T58+e\QHPV?Pd!X;kRX#KrG_><-9[jm]c,L7qaMDD,+J;Z\UH7HEm!W<.]**p:Me`mHa/=H&s4NPS*@;PhGR+C\pC!#rX\5asCRR^Oo.dNDRV1WW)N>,Bij0`m'C,=u#nJ^(%XEGG,onI\$BeDE7?2'1Rj:bY\@HspRA[D<\H1$nBLA@#Z0h*,?G@GC@e\O:MdqcJ_n@Qg&GEO"Ld/,X;NA1/R;-o27W0Z`T/_5r?EQ-jh@a!-27m$==2lbmAa"KROGCGtAGETjFI>8Q/La5U5pG]+AN`+>-MY1kaOC1gi2Q1VoFR#/.H+btK>])3N$eeD8f\eK(f%Xr6fS/TFZul"[3POp<~>endstream
|
||||
endobj
|
||||
44 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 804
|
||||
>>
|
||||
stream
|
||||
GatUobAu&c'Sc@-MR*_^NmPbNXC\%RS+Hb!<nRaKG1C<.<3ek5T-HEhRmMW+HcDiNSBH-%s,-osQOVhe%IgNR\!IL?LU%,MOUTA9bfe,gKMYec49NG[PIUn7+Z,Auo9?,JaE%P`9>Zt<5r(d;a>(OuI5W7MTL*]BR8*u0.b"#Kl"luYVak1.8.Ue+jm3OY+tO4Spm:A[d@,$oF!qkOMA@&=L?\)7<mE=95$cFk@Lf#C9-H89Y3+?tD"._PHRR3`F].n&aAcMTbL?,5-_+dYr<m!*CYO"Rk-5;k>7/Ij=*[ngY2I!3CU^TdjikWgbta<DEf%[U:\*%j\.kA#^^2%)F3<Thq@Ys$[^2\#\3qT^SU]&V[#"@"rBc^\O>Qhboa\fnFPPZ$aiB;Z>KA#AO"%oa0_I-U396!C5,'e+H&TmC(R6^!R9""bjBJc9dtf;n'H'n>-?^g(i.%./?Opt!'VEJM2t]=5^:gk<L"Mk#r`a?$5m>aNjfoD4(G"Mi=BJ^.I(6j47teWhAGRSs@k'tnOUOfqJZPV/Y8R.,aKa"ja9Ot%@/dO<_4g%:Z5BZ*gmZBZ*A(ONk.rp-oiC5$@(E$3YWQo?H]2X@;%P.j_t4-kH`D0N+Pd]`XHd.>gg08ORk@IUK(BftUYGW%9=/-nd_\.uld>S&a[IaZgCR&iFp9"7]:Y+\DN*L%.MkHbgG\3NJq"!?&%XUO(e5=e_'$b;T[<JCLV&$&_`<HgSV>>l>GLh8YKS>1\aNdgXO&&sS-I^j<Z!?'JoC,c`\\U`5-,30*BNEC[67pg".&C^4T~>endstream
|
||||
endobj
|
||||
45 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2957
|
||||
>>
|
||||
stream
|
||||
Gb!SmgN)%>%Xn(Qi6aW%1M$*kDbabSZ^'jpRG%mIV:oHQAsQsX]I;IJhh,g0U23E+Z^R5#aq<GT!Anb7)Lsqjn]s#kB,/hcCO10cOXK@=6!iIPFT$qHY>;m'3,Y*/+s=o>?5TO"#\oYQ2;X6if1D^XUG*,t&VgsPT&U/B_@qeB^R[>jiqfVs-V4&bS2a'q"0[(3HP-:rGi%U*EY<h60JR/IPK?(kHGa(;e.J:...LOiFqZu?dHQEF(Y9I)msfh65qanUe&@=8$XC?XI!:#/k&5A?Q*,TUjKS9t$!D]cQ2B[T_@^ZnjhK;J?`CWcFP1hslu)aJ>8EX()^/[:U$G.0X#_bAq,#cDXnYbLc#<(3&(S$GIc+uSNbcTA=+p;4F/u:=44Vh!:4so^Ve6A2#LVA;hbTdk$/ON:d@22K?AI_CYOIAoJr26jpj'+tI'[Up]?"f/[\&T\k4i76SB-a)MLEeW]efqbES!'l2gRX=+BqOLp8/8f@C^[(8\I)M>b*^1OpHf?E1.l/>P8]^8E]cPqg>c.Lk7q/?C/t8i0*#hZc'cfFBRP$)f4p!HJi0*rH\N8a!(rNkVrlr%sCGqY3>U]-eR/IdJ/UDa(Iu9L<uD)GAG%N4CR[&?hubH*DpS(@kUDiD(;o^&caNP$Gbb/]r/?>a^hPG;60PZ1FCRFK8`^qK+6]s^iMl4P-GQn4N/@lqehqAr*kbT\m?;[@fd/aB#fl-oom0G3-M2OmZ8N15&TEh,EEgo!-l<Ip)e,(&"RYJASR(@1Y[))"9MK4rluR`"qTKIZ7S[K1n&H06SH%>&kH6K7T(aT&7[7If>s,:insm!<(.\q4Y&&ga\7u"JZpr1jQ&Ol(&7i8RKfH_.Z6?SPj>?J=n>6Ecr]oP);fsk<6JH2>".c(Hi28*TWf11UfAlD$R2<=h_n;[MdKT3[^9GU/WHY`L]#$9@^1YZ:rDp-d6a.CZD(MhZ<m0G?K;`Gb=^P"lQVNX")b+\/(G>9'25&QQhm"boU8*.40mgocu9ta6/+gF6'0<C\sFUplVeA+F=p%;o2s7*$S70Rq]dg<s,oo,m$LP\#<WX/pKlL<#=`s$]I5`b%Z[d57o,s5cMN/1-]-UQ*`Le`P\2cJK)s'mk]jij<Wh52?0Pr;0hc3c%aA@Cr^LLJ/\sUZd'*TRpZmA.@JVf)#dt%FfH"tK]Tom0rleZg;:iU9A+lE)["8?1CS7bbr3FE:@D<"BUd$NdA]Nt`;_C/"b;0]Y7<5fq`*UQe@*U]hUD]t0OYs^LZDib.GpjfP4+`5oD!:'%G>Z>ELVn-HV6@FC>Ca2^dQ,H/\7G\=Xtr6H%`cJ8+tRoXitI#p"%8H*s+Z&.)kBHLQ4]HQ9eg(r0&!b\23*:k0hM9fGitf)PrRLgK7Q">VRM!,($DT&.B_4GH#uN8^?s\CE#@R^U0r0Z?>/\)rSWmS1<1pfqETd?WP4RD3$W(B\M:!Al.>JkcLRrV"NS1@f)UCGX&:SYGq]q&d<MM85!e?=3Xmn0V.Mh*m%VDQmlqBk]7Y+RUYZ!b/IYea":JhC+XuOW=2'q:H%,fFkYR;tS6&lI919YO^?P*>j0gR:9cXYc;&#d_ld8^aT4CrdL"q/kRC"pXoPD._2'_c/%1]fUn$n/t'V<t=oq==uhZ>e:fGhfb*-%7^=(=2-A_O@,CjIA0cA#>(NN4JDlln(%2&';=%2\UGMjLNXWB&uGF*JI%]-#ldBU`Z$h<d-u[iX8R5,"HYk!K)QIs@R8`1IuC+puREIu^RV)o!M7GLlQ<"+EB$gn$27#lUCglt6-ne`kj&O/oBqOH44E5@F1nCWd1O@tkV8Ot#jpn>YM4QFoN(cE>\0+j[[4%mB6b.t;AE*RF@M888r)6-Btp\O3\kcK_j'ga.,aTpnZK;63fu;bZC<s!^%;e.(-B>-I$Pa<^2[X'h0?W\R+1dti(l*e<&Goc9+CGeg_'`\VOFd&BC!os\8Yh0'%g7%Wdi2)hV+_#gaT)),ahYu0U6D+j8Ki9X`uB?'SM\u.Z#C$LJMVg6n*h>aLfc_Ts-c%@F[Mn6qp@OV_W+h?"DJ")3sorXjThAWKMaRWcOqsi_p1D,*pZe3h-qQn]I/'c>T^Z)nBCfCGfEH(V4#<n%Ln<`V->5,qPpCP)(De(2XF>!TUFb"H*NQa^B'tt]V?').uOY)Jl%K'+G`EE=UCK9YkoL_&J?Ou6;B7?U:GjS_=W)iR3(DCCS6-(Z],.Ei;Zb(SK$QiR[CLpSS$_H`U5+'Ne:GuXD\I_pS+^s??6LtCjW=96<5tL4G\\FS]2'<tq\e7k&GFgbS=TB8L$(P[+ZR6:%<b;f&_#oF60%aO:^6<(*8fntb"OFKEV?U/O+%=r-"9%\!W0dEKPkPYW4\5&U^lA<mc:2P2$qqVOPbo(Xn8_*9A#+*,fG\s"VeSB>0f_Y.d/n(WG5),-DP%Q@lR-+KFAC.AA5BL+hPsH`j,u(2QPi/=28`L_A#>a6WAbL:`"$I&4W4NHog9g@s)\8)fZp1^]p7t=l9uKGh.kX(J<m@)7G$]+pY([Y%=EYAfEc9LQ"h3>)*.>dRQc[Z21_GsB)=\PIY=7XHq`&b+S7A+S*\_P'igd&H!bHET;.Pc_Z5XH%rgdq$k[C,!o(R*G:NL9Gu;&Z]3+UY3]"BKpWA@i`/.J!+T>c!ZA)Q3F#1^Xk[F\?$8QRAKFEIHk-LWT<]+6Ar/=[V6$HJ)LG;%8c#$>UE0bBOL-+mnCP$#tc1l[dZQ=`/&KVl3^.$*(0L)jA!qnhL>YD8UK'g?1Fn6?.JSt+uRFY\<#U_l[)VQ<^puf]hoSd*^]KD8$kkWER#e'/`\a;6MW[CEJ(WBpr!KO23!0*MbBc@Zd2+6d8p3\W<k(\L6Rb!@kcY.@_rPWA0*uH$=^K1.HVu[GZ!u=oDjK1[)8:J3%.X6.6$_:4C3(Za`k&e^='l*:QV<W^%8Ef,aXJjP/~>endstream
|
||||
endobj
|
||||
46 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1340
|
||||
>>
|
||||
stream
|
||||
Gatm:gMZ%0&:Ml+%.`IbUg.4o>mg5?h)L;rP3aLO4rFVCM0>+eUe46nd')n7<9/;5BarQa*%2G4k005L1M/oBLCJaV#2n)Z]XqRe3%W[s&sC'$&f/ZS5>DKLH-R'GN&-k;5WH9uM*\bTTHm<3#-s>DB/0Y4#j!8L',oG?:C,.C-od<a#WG#SUnI`scCXFOE+O9#KF:<lCds4`5aY=InL5Du.13p0:EaA8A2M>Z%Eq"CcBn-\:LC<fE5$jlq+:pQ2ckq'jo"ZY/0A9qG4ZH\VaLah)2<Op5NRRI,l0a7A`%1M=]kIEWGX"-gXN'R($?II[q8oIg6B#!rYY"p<(&2jq>F!9e'>-b+tR\"UO,bHN-F0lL"^Y&)2faaP.ItQ!kqu/\t`UH,g@W0%]\B-GVVeOr`Ao?Smhtn#PkVuE%tldA=!`s^<#IF$M2r2;&S\_,mf^8aAC5L[u=lI(5X<<88/Q%`7-"G2X-ih0^gVRqAHZd"1$UV.XRp5M<o]APm7.lQr5fL\Q;/;r>qV]KTmZ@0-Ke:+rGtg&sLZn:W*d*KFB>H#/5sC81"G?JJh3]=T/W5V08lhIA]37:7&@i%HqGT%U)l`Lf38;jV3r&X#J07p]Pi;S]!gH0-[_r'ZK"=<F)j#AF`aRgX\F4@)7&elTLajG4/2b!EI6,5LmKs%3TGW]$jju#b+'jR.!J6"c`4]mtP=)n/FCn6aY"2QG)/I6-nTlN\b\AZB%_&[1]I;qQ12DDu*MHa*MR<pKN;$9jOb$!:o3<+s9R:,`fpUf&j\b^^np>n]629J_%QcAUq<$o4UsO+-Wo]PiUPMYlibB4duBZm8b*#3or92gFF'b8M7>+$L\cjh>'&o_l-$._>IW/$MIi$BVNgB4l&:df;Qeg1VSch2RYrk<4eHR+u-%8a._/r&d$A?,7>ZS6<I38W=WbZd3?aU-KkQ8$Iqe;dg)g;8o[N^i%!^ia"85)RKp8_[C34UqPpMfLOH@rWQccedQ1V*\?1#RGA'$::fX^,MgGf'A_[F1Pd\C=Orp9C?_!`:TbPOp3k"VfXa0F0%X5a$+pkN=;obEIH%dViS=[RYLX5.Vg+>Lj!hBXOet]RRh:)dS['!8;iB5DtC4CWU<0)%I`7W'NTG&d,Z#pZq'RZF3PJXn+mq.7ODbNaJO1H`1'!=2>DYh%5gspW'g2<Eaf3rg[A62kDO%o=7ik&qKohKeVnp[goO*99uF/d[:rmC_!HY'B+#n[>8V&TID$O>0NRnDN%a'j0)rIeOEDV);Q<ZY;J#B.lCl:ond5&Mbj_AO<dgU&L]23*^qqBQ!dL(19rr!J*<*o$8Gr-LH>akr<1g%5~>endstream
|
||||
endobj
|
||||
47 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2607
|
||||
>>
|
||||
stream
|
||||
Gb!ktgN)>a&UjCToV9']3<XTQS*-5VH$2<Y2Z'3u(JI+MAgp'abBT5ScN/8&AVm^_9IcIQgrppm#5[uVmK5.`nXJ3]dl#P=)81@BLiJk8M><0-ZR^>\48X?d\3V*UT,bjR&16IW.TU1Bk!b-m`*p_88-4Q:5tD#t>jd:_+_gc$RDWLc9/nq\@NNUIr5:`RSTXNjo..\&chnt72KRS(7V7Eq^HoMDXY39*E5k8&RHm7YN.Gc5>[Ld^0&M/iq05JLhpLiQh"*KH5/-o*G'S4*ZS+04WO66;LJD7D7p'fe^<7PbqMkH_3j%!hY;EpCX$Td,046;qC792bd2SW0eO&Dt'&!7nj0!W8!9\Phq3rqX\`\=.\DoL0\]]Wfl.8gsat$WU)B`K:WmZM'a+NSniV*s4N!%Zr.=1sNIQESf\&-2G&5*/2Tc]1TmTH`qGHl:2i6Q$IZ^VbN-hO.#,*+_b9-WjC?X<PO].S.VZ['l4en9MgS_H/"_P.T]Fcml+6YnjcBO9&rb[Tq$0(>Y+oAh2ao%O=;+W]M!&iZC$Mm5/')M8\^h!r-Fi+MudEX&0]*WuI(MLCOohk>rU](p:Lf,hQKYKce21S`\_o_+9Vo>9=DYht2!554Ad/@F#<Zq0OC'f8[*OOeX[iYo!QMm'OgA5sFt(7MB6]r6.BgT:0lA`a1k/k2?m;'0m6mEqY;$B(`gij'gdM"hC`22H@7HElp@!kZ-a#OI5N41*>94`\3$ftP;UgCKtC"C2G"VP5d10:3K-&`WZnf4gG*8h>+i(1bPlV0]i&AL2#b$_X1cg,+&=p/<;4Tb0h6qH?Ph8edQ5*lOKMX_#$Y*H8/eiEhb3(6[sg7c@a/dcD0C@T`t8+_hD]D7pO)S0V%3.^]fq4)$:(<;cOlkFNk3L-7kYdP>og&7h(U5gVq,J1uF>ff$gdO>rVIJY-3[C/`OcmV[u/%DhCU1N[QTa]Z`5oCcb7!_'EfA$!ZZb"B^)BTU3b2kSt+3r;<]'NiX`[g4e%*4*hFZ)1Z._mcYI,`%,pI6_nD7e%f-YC^#U@WXWfg\Q"A]Aq`D`1'G,aa>8#<X<Fa)Q>=rXNsW8Ri3d><QE!+/QFFo<2uZgp5,2[Xo<Qf=UG2(Y3W/e(E9WAfp$$8MG,-'HB6:+\JKRM3n$WchTA<`$Cd,Um%@cB<aMf3<&tgd.7n*aOi9902UecskLm/Y;7$:LSDi@p1Y:CX".29P[*Y3T7BZ43J"A="_Gm\dQC?dT[eRsb,E6<@eS%RS-jj7G1;i<>cm?CuYQ,-n+&i#JU:?:mf"K.G80rj#.bgb\,S9u\R#9MA=8>1#^fa#?dPVAARan5go2(fqI>eYQh];/@IG[J!>*C&\LLHf4YF]poIRs'MBAR;,$ki;BrS7s>L+Vs"]'*;E@W_hZ*A/0nXniNP&N2DB"aE8`+L[kY)5l!'8u05N9W#AG9Y#[3(N^mGjS1ML"(Lb5L[u-:lu8^tRmiI!kQUF>EhBH,F249k?7.G#j5Q.>I[M7up@Kb<gmtZZ%n(f-3K2]5Ikk"H&;0&>o$\9QAg?oeW1D^Shmgbss#&8Dd4Pc[qB"<.5AD:k]b_G?s%k<57FZ_U)K\>DR@bRF$3g;qbHgWa]%9c:X$YZl^&kj8E(ZI>oe<5s8f(rj\p8@>41/V&e64p*(QG`3&<LFPU.X36dnh4!bOe)<V;l)Ba(aNs_\mX4!cEQ^K("5Zfl!uYNrg*30MI8lqY@U'X%emVd192j`6'6SD)eq,bU<"'P&%^:!#b\8IV;6%6HRZL/%ni+e"/2C6=mOk73AmCAb$[E_$c;8L[d.B#UI"tX%$fgcg]/.@?_8>p/g>=WXsSBd5NB8;;ONNrB&:Q!lkT12KW7^nObl"ZeWEQ^RIsSQ*RF:fLoDuU0*5&hTM7#<Hj28!!aHqY're=5)(@V^.6Id"nOCY;G6Y9m66P^qSGcE`\_VcX<:1)E1Eu2bcqLt5*:S+;u5lI<9<4RK8feS4$T@#hGb:C"Zr+[D3I:Ijn5Lk'm/#B,IFM6*.D5-GhoJ&H;YtTbYtE?9]KjEii.8DF\4%8.NL0[hg+aJqUo1OTKBSOnar>Ng5[%cV=#p]m]l_O)XYT3)#[4$1V^2/IWdV>22;upa&S\D+oK)TFaFi0WIV,9P2;XCPP,o/5E3_R%bWX_qp>E46T85]Y`)Y=rLm_pTZJ14[SE2Oglq"49Q%&*#)I]5(IX]IgVi6<VWX6,TO\j`ofh"G5Nb>bIb6O-*2bTLejCXH,u..FHu`G&05XSC.s%s1n\^.JI>D#["u.V,rt`W/%1TRfh&\]e?(nKPe+O=[/b*e`dJ6?4U$a'W$o$,fZEV,-3W@;ONATtE\W,5^NkcRlb3k^.07>PYhH3/^ZU\:Oio]DOi62G6ji>:WAC_-&mX8Ve<]<b%4;^uMF1c7tWC]4k(]$qV^!,Tdg_chZgNL/o8e3QkN3U!k1cGW#IOAMAYTFq8EiY#mRl(:!Yc]d'do+:m5gQk<#LnLMJ6(%Kg'F$VSYtV9XgtkhTk=REYF,e<lS^StTZWE>*n<Qb)e<%Kjm5;DmN+qPCAb,fQ^(\uXm8k%IV'+`WRqH$GkG4,Y\a`thg@T+R*P[;.F]UE~>endstream
|
||||
endobj
|
||||
48 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 3022
|
||||
>>
|
||||
stream
|
||||
Gb!l!=``@V&q8H9kVnQ$&f(K9<:,=h<a<6_a0IkbI8Q7;k$TG]]L$Z*XFL1l"$AHY3P;Yg<91=h!eEO"1qK/$JE&k&I`E>A5F1fJT*,E+b;BQl\Zi`$X6&#QB8h1G-s^4I$I=c`8Z;(&EnhI!S;"[1^l(<%"4%3J0J4@f=(k_9E5k;eSO`g',8pOigkbFP0"QHpr<0XeTc[Ycm:eut8A_SI&8+G49,'*0U^s0W/Dq[(b[>F3IC[#gl.)8iQgjn[hHo&"Nl8cWX.P`Ff2#iN$S6d@kn&!0Gsjk^QLjaZA<OQjrpq>jA3+W9\>l#j27JSnmZP)1>:BZ!<oiRWV=QZDkr,kn<?0m`jenRF>?CL"j#R?7`[uJXV.W<el2m-]d8aMG&Q+^e.7Oa/S]D'-3t_'OO1Ro-Y@$('8'Cj<mZX?*/A.)&-NZe!7Vec)A<\(uo,<25m0b32SKNU$&^1ncJ9])3gcQVP$>tqWHX)L."?7g?fu#!GpukcpH%+tPi"6mPnN)q(LbcgNITSaoB&SBs170r`a:918Bhf@06]_tt"eY'u/atFks2u8u3foqgL(6Q%&Lb-7E<L2dS=s\i3oMk-+$W\KgUc7cnAteT<g[O[9s8oX*D0&uQjjZAV*(fC7*q)t#[GgC'td9+7V<'7DUKlB\5B(t6+;=qFft:I9CIXV#k$B0)+_l$*Wh/0:W>j<Adfe,/g?qSisPE8fr%d'$!h0$b^3'.d.][eU-DY=1S^Ye,b@Ubf^][]UE_cSZ=/GY,69Kr4W,mJa['.Ii`ubhU4u[Res-OY8I_]rD3Hfffi7TF7_b+f/?/oVU2d;e10'<;(DRjM+qGAa\+X(kc>K._@D_1/+brF*67^D,1NkC1eWDP%l4(`M;'cCa7J7nHe]Nid/o1`IN7;#bpfW`(7?o9r79nnn;5#$ZjuI7`W@4>kI+P:T@`6U8^B"aP.*e\eoLGn?KW$T7ariA&HE8l!`bg:N.H$4WQ,Q@Jr`rU1XsU4d)(e?QaqU[?[>?>9[8'7WQ"rgn>bC$_?`1J,En"FS+"Buc$>IL/DX`aY@QR?F"13;=0LEqh!QfBmKaVsr-&.Zm+chON,I%#&^pLss+"+N^ebeC#mt'eE\F@11"!UKsNe]<^"-3RT,r("]2MO@'+-+Cq#i]s1ZcDB`1X753Xpamj5P$qH]5Jf]2c_F,&RLIde>V^93BVr%q@"i$\>2]uU""3jW0G,@EEAZ_::3ON"'@fk($hZuP9O)mp&X1p"Z7!g<eN5]$ggh+c8lg&-:DuT'baIRV<j4Z=m7oF$$16gP=AQ^]AAo2O$geaC=AnsMJT))j$9aq?4b^lUS_$(URm%Y*4A01qG9`%B\nAdpB"%b^'Ng*n&-"&c;t$,hW6*dYe.Z'YW9#&^M::CUBh'^p,5#3b4==qVgZH@X)Z*<L^;_ggVuN(ir9B)S:1MAmmNLNL3QT7j)f!/0M5hm/oRnqJW&4N.Cn[<*6p$WHf_+fDR3$,BM`;JOH[%4Le)c+i7H#L025VaK_)gUU,@K^qPgWcMW8002t*79Q5]`4:J00!fM?+up7fA]T$:]2:U6WIJVEQYJTrOH@]C0\0k+DEko;L8)M?0aef/^.X4Th-HNZ&u3>af7EtaCfHjDY=?,nEpFKa`Y!]-/1g811;7g(OLI8"A_=.f@'Z+r>rf;bDQ7D/jZnc,a/Q!Y9$bO/R1PC^*O-c$B<h[$)4g2n0r`TqK2eqD;[KZ`^(>psmQFEnu1\kFh[K-%9Y_fQg'dgq_Sr<%`jmJ"'QYB6dJp_QH9FHjsr(R-sf'rB7QK_J7Hdl:&ul>M[08U8]2Sd7G0LbS-f3u%FV_pYPa<Kj6QC8K3mV=Bs-=K38QdkqnKH)qZQ(^HRCP\r;pS<&Z(h\DdO=2j@W9rTYUA;]X_MU,8`28_NE:,`=!\sN.qh=:bYq7Qi_$hY<03FQHkg;=]lfe`mK4^;%lK,[e+p&NOZK[5_$.,Gm@1A1PANiLZW;;$,ojuY?Bm5e8X2.e8QV9ueC7<;[=X+$%V3o.oJQ71=ap'D5_/f0h9VTjgeh=B68h;u8TomVH*7XsB3BUP)#@^^8h>LU#n<3oqqI!'gBjFlAb96]2u&WBq6oDNNHX6EO'Y"LLOTd"@r4'bpS72Qpg6'FWhfP1J7o"g&04g`.!oFUYAaPlLXl-Y;R$Z-W<]00hsbed]\pZeN.gBIE<p??s_97.V&_T2Rsi-ZEikEt(*8`/QqV,*Hj/R2obYZ$#=Zk75X]]^$erT8A>?@@q9Di=7<l+H4:L;Mio;/I]hG4QoadJm-BrE<_!9l2RVO%[]X)8+.cR<7[0e4-$O\+Y*cE?;qg2V39t(F3IQhsGX3UTba/@X*'XX`9Ca&J)M0o5]&,BcMeK\tW\Bgsp&&<!?!;:-@:B`#7=X4Mb6apNXu`FKEXi=mgF`UMq4KMTk^I3$/VHjL"DEW9j-o25m@"gL44)W&-sSeb,jqhoE%rXt:g@)[GCVqc<N$Rs4]1@rP\Y,O671k66q0\b.:DpH%\=Qq\&m<i11=UI:PdnhH0V$Vd0FHH.)99Rh_1JsZ(K9fDQMESd=k[Wjm+FqT*3n+W&+0oGEV?@B=5Z<-fGnG5q1/R[GeQ",T1eqk6smB4Y!6habqKh,@o915-@"]"C*l1)4tcDUa3.NN1qUg;h;>d\YW"soM%8_Rk-eL6Y>[^.sYB/*P*B5uGG,!e;IK5Ss@gGrpCqOXS<$E&6u:C44Q5bm.nLU(VrA"QM?@N)pUX#ZolRPE3nCC_R>-i+fBagMt\cYdp7j:K&\?ebt>r<jEcDtI>3Cirffd6D0sD;W*X%sE>_-X;,9imOIt_F57P`eN1<"cKLZF3]<TXU+9[D<RHf7g1:dfuk%g$fYVq4SV[*2LT3OMIr?ElUpIj[X+fp\/<J0*EjMfSqI.[AoPe<!?j4;oqNPY_pJG(1>a5$fRC$D`ksj@8FPdH;`23a^KpK8c#0.K^HR71L6iYMd+B"[TX(m%[RlWZNT5nW3ZU7h-/I32W0Z>,U#G?f2&=(]~>endstream
|
||||
endobj
|
||||
49 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 804
|
||||
>>
|
||||
stream
|
||||
Gb!;`9lo&3&A@sBm*Wps.%h)=DJnI(S2O*+?E-bTaOm=6JW!@&5JN+e4&/[t1=K0tOUPjrmG#ZK$n0tKGh4O_LD&HN112*E'sNpu:bH-^:4>qDc&.+N5T"-:/qJ!2l\WSC+Xnl2S3\JX_F9bX`X(ZtNY*11V\?oSRRdo6Q#QdlUlt^%SaH"=GR9=)iWZ-63Eq@b1+`,%nR&`l77tE7c_4a2#[1VD$e+F\XINRF]"+9G`>ic.<K_;@>g5L!Zc*685G1-pi^EF/A.`0ValE/\'2I!gs2*<7X)2f0mt+W78i!h5[=a?X!U%b2bYm3LW9.Zrj/jSPL<VsMjGW_;Q)'6=:?Vu0.F6#]?0B#UTY5Ir+u1mh*>CWMM=a[E7$>4(B_nabgB[2r;7Z_\5$k$Q86J@X0p1s9k^)HQNVsnToGO4194Tju2b*:gA5!>P1bKk8j!,f&?u\50S4<X69?JMX:UWqq.Z-&K\h?F3WIYbO(o]rtO!5Y96-)P_E]eBIaRlWh;L>h!I8I.f9+A9fb(K#Re*tQ5\o2PfTPkR82e]YDRTe[-JtT6OiP*k0"$-UcWHX=cgM&S%/urQ!Cr?_=@j0<Tl@_83p92p,14T2Lp0E^aLjA$PX5',ODh>8u`NtMEmGV7!AshMfl[sD[n1JEojiue.YO5S>2(#K>?7RQO`j6Rg:sEbYcI4^)n?pugO.YZEh7*]D^F7m7;*h=S8%Vf#rVI#q'#.+(0"%R:%TV@(]<4n:KgorG\$5C?AK'BeQlL29@pJd$gUk/S`\-pRisR^dlAY.[XtrcN!$CF-S,~>endstream
|
||||
endobj
|
||||
50 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2630
|
||||
>>
|
||||
stream
|
||||
Gb!Sn>BAQ/'n5n\3,_Z;AVL3RgZa?[[56XD]3/@oIAf1FR"T$e8:gk-V<R?-mf@ZqA4p'KRdj?m`thD/G!:<:SF:tOi8Zb9^[hfj0+3#G7=\3:49,]$&/Nl:\N'J2k4qlCdL\N-8JGgq;,HSb)Z(hKn"k"t+Nbmh-;b0`nU)7nfRh#Y2eO1'r?.lnKpfUS0*sH7$f/qO0=G3o[L_^@GcC)Z0/9^^9ZI=r?..JjAKE-SL6VI>ihtG5K%^%eMoEZcO1.%anGOZg>qCVYil01(h\I$[Y@6rO/ACAhZ(o?GAB.Jbb2_OZe>%KFNW6]9gPXTP_PK/JR:Qql[E'XF.8Aa&X<0jHlWg[bq..N?$/u5Mhm_ZZY4OIT!t!KK]A]_bU[IF9>(W[^=1(F-E@&KCSI(?qU;)CHaLjKlr,C*PNK)!HLtr$;I5DDH,g0(Y5EZ.+E)o'd#]Wo5)"\IsF6Juf0+O-ZP(7chaV#jU55!a\mDQqee>=Iqalid,0&?3j16TScZ!J3bBoFRg&@EW!CaLS`'$g$B1N`1%0nfK.7krg#_?XgM[j.bf5i3&2o@#l"3s:#JT3onUl%DM<,+YN<5]GPaO&p;tmL2he1b?AHbK%pAmeXV@GPo,nrjIN-[bL&EESc8D/pnXu&pm):SP^)(CTTqc>,uP,5o^SU.5_ePmY!L2@Sh#gLFdUjIqJB&<=hC[*&F;<91At*m63O`Cm8peb^>Sm(7gQF5$N+5UBBSKA$\DR\K*m\*P^TUDtp([Km3b8cKRS-gl4.fG(*(:ZHk":M%)i0hruh_="/U1`=NEp1W4nHnSHsP>09;/E)@bQA,u@Op\kZ;\tQu[QtX?#dp^h`A^;u4K2GP-%/2UC_AJ&t[P["A4_o:fSD2).cO5&:gFWgG8cKo+7t/D(h?Hk:,'ZBqVF'T)_%V/L.j\LYqI>j58Dn<qC?o?L*[IkM"Tns'aUENXWa[#=B"ZT]8$CG[^gT78CE%HC1^A]pEVUNeKgs/h;0!*T;ojAfFO&7u('c0Y88gb+p>afX;mPJGbM(kX:6BUW3Ihj0W.iD_@O(o[D9_WV[^B8+cco3<#t?ahjn9J]%d_&2%"iL(_a=pJce%2*5Atj!3+<XG8Jh>B/t94d397T@NrCZmbc^&2e7`\&r4HL5SARE;f8(\u?>E``Cbj&EBpt2\5_I!&NQ)LD/0#EG5t/&K+X('L$j9ls2J^*X\(>iNaAV)Nle_pnb0lIXj[KqQaZhKg`on6K=-_.GnnOse-cL&4#EP%&9u/.K:E#RX[Qmil"G70_kdEc6bXuC#&[KuY%X%;3_]<62N`\c"i&+n;a0pCMRfHD]:E$/dkh8.,e%j,GCAj3CFod&&<#f>j?^L>T$'"5`i!m_6(k/j8-+mitR=J>FV:?aB'()_Di\EJsjs'qpaMUb.St:,!0;i+,\1aqangpbYGqKi[3MNsdHO8dHAm\WMm-&9JN3d\9`p4(5HNqcHs"!9'cEWV)_gNsu[ToJ!)>\R8aLW(n%@Q>Ajg0\Mej)u+ck^&1g=i9R@\rj'0o^ah@^YlF(DmOR)d/)G;$n"^*4i$olG:=]r&BGO3E2AI\lR#+'0lqd#"t-XT:r]!ciMeo`cA#5.qcppRp*D9Mnr8,7+!2dQClkbe*?CJmM1PGiX"5Ph;n_hNGRc&!HV<)]^/j&9]\rB5a<>6eL,quqJ`JL8)%+F[Ddj;[H".WqgBPV*e*,H6hqUj>7=RJec_'-i\P3S5IZ`_g;n#?gD0(]SjLmK47/QP"/h-5NS/'<rTK%UYr431rHHUEcc*ndHt0jG9][\"M@"k_pd52(6E6;o<J4ek*#NrHJ(S5?9,tOD:f(,.G)L2g?+E9NhO6eLpaL_TMXliu$MLK'O7^JC+e%bqBQPU=:/<)<0;alg@_gPa\[-t`_p5rK1^[(@r:eVf4Jn',4%;Mh9H0qoQl,\WU1[t%UHQ,&D%2/]Sf-9jV/t=/pbTS*OiS_rL&1"uH*f\&_=_Pp"JT%Bd[o:t0o-lpPh"EM.1D$2,I+mY/lll[[=Q^t,Fdr>L6lt+qu5m*mCPC.[]JoSC.tV1.=1S7@s?P*RL=Ia#J\)^CXuiuPfU,ei3Gi/M>GU@ceqOjYn`a"q1H]'8L$%$Q`)j[%#G'Lm"6fr=sMAb*OkDG*Gj9S#dnm5>`&mcgJ4l:;]$5hA^lR8^b'S]I6]+"Z#8$CHTm8`Z)[n@<n1GrGHg+HD3dISmkIm^$b>@M8!RrdlL:1A%@s3q.J[IMdBL$\IYU/>9"%G)X&Nk8@kk:44O)JYip=E3BYKQUT>K`S0+&PZ"FC449<W5/8ABg2VI-JoD:Q3oLVi:W(G3/*Y:<_epGo`DUD;)bF?$:<9YglZ>H[?nK@S%>SQI?lhCu'WG=J!e=;aRV@A^[N,4oPoE["qt_bI@UjiZRlXHjlCDgKe(c5u=TXf0he7o;6&%JHEHgi-9#I,7qS,ql]LZf2KI8tb2<+ml:@,k/oBqA*l$D7$W@]!;[h^&iIl`[J)uF60!pqTSMP)cdJA<l5j\pt2kjQ-]/'aSoP9*Gi>$TF'.V=%sb>pEXe+]dPU,Y?1-L0$B#lhYh/ZftG_F=0/hL_"tN@I0m6M28'-b_h64igLK*_!bu6hT-\M;q[\3p+uK~>endstream
|
||||
endobj
|
||||
51 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1547
|
||||
>>
|
||||
stream
|
||||
Gb!;cgMYb8&:N/3bbH32lb2hSLB=Jc(F\Y"NR[YjJ?U.tc3=uLk!_T7opoAd&g^2-MA?4<_'[-lR@Q?ZAnT28ig9K@kn;q;.GW]7?lZ$O@$4'J5/>uroJ.UILgr&g0[>8(!Q(OV*YkUi-'Z>Va<4"T_P86,J.]pmL_:UAmm_uL<$WH>9\^)=81*sJl>3R=\ke"M_L6sQ:8`:jEZW4>#Os8/@n@<M)%Rm&PXI>t_'N>K4cH<!qqjS#No@kr9ZAk338QdSI@Q<>/n+S[VXJgmc?`"_B0X6bO5$S&E%3sH+)KX`FjX=$X.)^=1D-j(P?O>n*l4s1(+.L!C/k1UZf@a80YlUj,Pb8>oh7nbJJ6b*kEH5-h&j$.;`)'8/:r\LR)TUo4J"sPKjQ)NMutP*b2,U4UQ3FoVtBL]Osin27m@aq/pu/43i&Ua%KcJIRCQ@/2!>Pc8PT>Q7*'%H*gjqiSMAH0l5@n're)Y`-0Vo'*L/I=V*JtVMB2j%4_.FQRD\j!`M#A,EoTsD'qF%=UA:l@<LULeQg/MD2KbA@8scQ\&R&,3,1WQr:o[,Rj.phPW#G:@dN#5&'IHiQ%SheAips151.C#"%ZG\$Wrh=:h'_et="BL"*l2KjLothT%B1p[#Sf"OFc&p6DfDCN1D0.NT)]tf&oB$.1D5QnL+j[c9-UH#&R^DsHSL860$/XG.uD:h3*bL?0:rB$ctSY2To9gp4*<3:Osh_-*@Ob!qmOFtg$hN,`]a6GO-g;e%r*Z[%VacEmIe"_?\"'`$n`TR2)Nq&kAP=&EkTlZF(],e^>="rp;KPVQj%LJZPLgT1t7:Or@@kq)7_*RNuLkCLR2O&$K9Ge,9l&]J''?/,'FZ@R!AR^GssO,]U_I<&1#qJ2"''&'0_3YQEjgHa0">q4KrYn`a#FA:cWQ:M<1;/.o=R'aHZ-A=('56g([B;$9C0V\QP6p<Inm(]0pG96*')DPRV2EeePq(YVESR^U1T&?"lCM@!5BkC5A8^^Y@"]YbO>@6.djW>(s8fGY6JcaR$ZAe]#\0bg%h:!+2t:#6,I\BBo6DAsCE@i31J,WQ/Rq"d?tuQ\M*2VT@!(A1^^0Og\5gnQE^-[$/;2dL0@>>]d3QpX*L1XO5dZ*"N2=3/r*`a2>H9Jp5l*Rh!fI>`!No`Srh[Y2!+4e/m@>q"s9cq<XX!]6-h2mnUfl;pW<Rlg)"!!q*Y3)nt-"knGlV[bW]XTjBlREND2%ZWMU]^B_Rd;^+OE&0cm;0K08]^X'*uLHhlT:Y^?[R\?@iSgp9@g*<Kc7s+Vn`;aJZ^ND2ZP3)DjO.#Y`_QI5BC&MX*WVaEEo"WU2GPm_nBV!kWne3Le2S'4f%6EA(LA!!:s+&S)4p6&Lq=HYpZO7cACRhK].(-oZEsp\:,265oJukH+bUu&7p1pA&S!2/rHJU<B8>N]bD0K<o\__[+)WJ7^Nfc<Xe=`JR)0V(+Nk!/'09$[jFH*dhLWXcN65cEiS**^]Ch5Ael1]-2cCafsC/S*BJd^C^5M%F;chb[0TG@uD3+;FiDpPn1~>endstream
|
||||
endobj
|
||||
52 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2232
|
||||
>>
|
||||
stream
|
||||
Gb!SmgJ['$&:N^lqN9T;Pe$"H2o<EY.1#:CP:'Z@H#.7hd[608VY9?)4h=b8(dq7nXTN.=Fd\X]F8DDER01?6mf''e^eZc]iIV.J0O"PO4We:&E'?@p5Oqnfa2/(B$Qas-GQR7&W5SS-KR%PSEX7iD'2]<-^ca#TLf\7l]KPh1b[_p5o+QEP?K<L#RG3":?V!Bn?8(OJ]U8-OQ5J!Vj'dL`P]sAXM'oe=+m.",=H"%3E-u_P3]k$brcE8in'Btal)g0Ohcu`P2ESU""1'X7HeXNIUPE/@j$u)jrpcEG"S:C<hj^u%FBKPBYA1-'>8cpAQ5[7J,(_UL0!`#?dnJl8PO/lLL&hLn*Q]DW<k$MD#)(%Ni<9l\qsO4$5ZZIiQ3KXnQ"79?9t5Zdf`lnnG[VRGJ(!`o(D!u"ErThM7-o7tP/KaQ*_@M\e1Nd7?'M+74sDf[)^F<q15[6<g^A?>+eeEWF([EFF69DQ#%I0I"XG_Vh;]eD$?9Rh,REEA=.UOL1?`610eO)6n6[-uj=*Fb]*\1?3WanaT[7DkfF>o8^gi[H`SVm//&&_;+juJY!^o),IE?Kb+iANnl,+*fp9e7T6-I=K3IL8ghAp$/#MA/GGA_fWa'mhrVG9>KWl.q75oZ%Rel#Jg>`.en=1)3K>:>kS\B7]bEij/GQ-5_;+&C3cct6f6KGP(ZO*piiTHST#6qeFn6A!2>\A8;@+0@rZ3?#4I^aI,4.31A-$TB,OZK&Di@*>*kA`*aj7OG7?E#1@2,f:dFF2,T^26<pJ2^fdcIkcs*85$``@;9<:XL`>U.ROZ$=rf;?M/PeMV$NZEo`1fn2qBOcWle#7ZDn.md=a?%bl,m_T!%]`'sQ9"J^g?Ag`hmC"uK(VV<6(g!Z+[E<NY!b>,&I8aZWlLkFb)$A5C$iUB*TGQ!;;[`5d2\Uh[5^*4h'`H\0n2GCW,W*I(:ra*m)mWU8`sYo#F'-#0(+!`S`EMeSSBg?,V6eo(/m*;,'q)766bc(9mM!WiQi^/"1j8o_[7]Ushg1rS;hl3knr[sG9R0<Af`gh*G(64mYGd!#@c<nVLnT;4"1TRS4.l?n:R9NA(_Vs&qlRKCige64f=lKI!PenbhBE;bURWC>O.D5K9BreW($)0/J+-'!t,2PAj)<(!YM`i>Q1=7>_Cj%G7ATUGP%HMW5\s#n64W:V+djM+#?FK!eg62%=;am;VaVogfbjXX?YQCa(H@?'#k?EH)%8iBal4^Fe7.!cNqhRiqT),'>qeJ!>o81n&`X.:(<Fb7Endsa9>blGd+HI6eYjN-Z&C$o/-"ZKGIhTA[WAn`a)4eA48]`Yi5jn1_1\LCL7nu6Na]E0TD[[X0gF?@A<L9u<l5$DEl`M9lYU3^gB`SlmtK,_)YbS8,5M_nQZ9GXWt6)CWbHEC$=djAFsFeIn3j?$A!1(jjBPFW(rS#X#EfbFQ0*AgTZ+)]r1Os*j3%BkUeks-nb>dDf`P7fi,TTt+_qPGjGba7/PgB#AkP(NsP/+^&Ec*LlM%odOB1'L\jKgph`]H/&;aKEg/kgBZTS\QP$9CNH&.]iEf\fi-J%#_/kf1;pg7jaXq8Za/nfka4`>:A`&=8JInge7\rPQqZE[9]L@IiDC\$Y35)lHFEL:80V1brM`"FpOh29gC(fcha+a[TWsi'nHG(k0uK%T>+<SUPb++XIAQc=POrU>>SVjRL3#R]U3?n$@.5kP`iL-<)0kkr=KBVBl',ZQ'3Ie[6'<0<S%OkgFLju1m5sh-eX4(pW_@6>Mfqn>p5;`R?QFManQIBP#>.;"*>e5'Tm#AHqd*1K*mGrkD$PbMga(,q'kj\Zl2Fi>uKWE9#:nu"G^7uI;[Wi5%Is4LlLQ4%(%PO!`e+=Tl)s0];EG#Ermuno0%oj><PO,55b#":/:s(,n]shpY(7-0<S,h?D5`/*Hhp@dV.H(`UaR[e]\K%be/9J5B?TpDS=7,&Kc2iS6=Zsj3W6,J_Y@.9%m(jE341])V<_Fau/l+"BU83h+7HAep;&NN'JN@Z`.\EoOV72J7DG?!M//QM]HG#"niJPMHi[.jI6onj$njH]%<B"jc&^)Z\g:.\D5d;/_,>%[PoJ3)j.8\$K9(W:H_X+=_IP]pY_*J(&EN%XXZ0fTmsrNAW7Z#4@6[$*-fZ?jiM7oQ'ua>,:HpiQ(%GC.*=uMeRf.2#Mrq=ZXTuhYHe^n<7A2PQ5ZO`G)I^]e<Os[eo:6t-A8Hl~>endstream
|
||||
endobj
|
||||
53 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2425
|
||||
>>
|
||||
stream
|
||||
Gb!ku8TPh0(;B*9]H`sb\$>Ro)roSMRBg;Bi[b=8WLtS/+k$hTMk$U6j7=>=h/"]<;'$at)0mB]jXhS2$?9JKLYMjX*8nF4]R%bP$WsK]qjn=VGOT3ap$'V/5o^7S4>(ji-K#3/2HL'RM\>0k@E9T_!`)3M#Qa[eO<O&=Yl<e-j"ZaS!7+GS!++_6m*'J+T*sm2_YoXH^l.GN-XoIV`e/e$b<'!m`"QC/&9Hu&/B3Z0bW`\/0BR31J%VAjpM\u%^6DORi;1]ZMtst$YkT(OhfNsrHKYN#5=1[boU51Oo,[eubI#N:d?O-tDCX0fA\^5O/R^(r"L$!XD-"5=NQR9L9rfJU!0uHnjIa#`QdV3H#`h":Jr.:&r_]"iMFLZ9M'/N<VC>"DV_=2"i0T9:`W>Za]"MPDE#b#;YHC[>^)&(e9]jA?.AYMJC<(b'j[c);cnbKNOmk[T`=PW/"[%XgTQ-7MeK&/FXD*U/Ykd2Y&k09B]S&cu?EBIr@2u$&$)/c!?=t=SORNlIS>$h+#f5sH9O^$aJq5&pM.19-1*S^<CeoP9^u+\>cW>pE,_?#YNac@9PWND8Jr`-$Tk^3HiH#VR5;Th6mbZ6n]a;.%@FrMSIN&"TAno6(A<b_F.W@udZs(lb/XNiU/lt(a@AVpQ]iiKSVPXJ*f4CWpo:&(!O0$_Nl$b#gB7Y-M8-gjebQ*XkZc.c1NO?o^nK]B=I#b[1)"jn%Zp8EUCs_kYrK)DJTYID!$p:R:g0k\:5D7!gi)WXbbf%I9:KRq=PF['qFB9+FK%YLl#<[ka*Y_T37]@=tb8aEFX-J9ZV0-\0gk5>YCbo(U208k.UFR:)<]IM;RHhUu<ZhoY.6tm4Si,dCf>2?t]BNjr&\YcF*@.7[p=i3gXWV>KT0@M3X\>`j*7s[f0JjL_[S+![]?I)9%$d,:*)Z7a25tLOhG\7A+K>&;[74.8h&M@EWRV:Hg<-0?L#5l7NS.M1e$<X`\4f!V+&HUAR8rLBnA7O#_*$@SaQX.3N_JXCOm:=J@Qe"I`$coaMc(c%%UJ#WPWO%C%"CEilgAH'$jAo"pKcQ7FEuQO4QilMYV&_.Z>EbJ*+AhS%JOo3%RkCNgHGr=8N?GCah5[A!W7Ja9/36E"CnC&:saFE*X$s.]*#?7OIYAMWYs]r7fH>M"/\$6je%+`BbXi2i"j6QYh!L^PPdURad:ER:mGfpf;:+m.[G_ALqeoDlO81j.+%9-8W2fU8DgdY>s4RdX<pN^n-c)48>%D<XesL+YZC'`/oZ*T$d":QN$@F0LTGa@.s*(7QfMGNqA/*gA$Dtd=$=T,F0@+%20F!.`fhbWEE7h73KbCF/'Bb*72PNmN8n(:eX'F`Zh!Icp5CHBT&!qRE)81,qt6\C0seFX7>UhAV8Q4A@<OQdk[LAnhf;5TXi?Z<=</8ACbCQ#aj]>DfdV22oMU:_9PtcK?3ZNG//:lPP#bZq@2ZAqoM2@<>>qIEeYXhT0;.W1ASQF4;6Ek-p_QSals:\[S$=tD:K&PNVc:3EZc9XoaF/0b%)HiT;/]H?@sT!14h-X5.qtr^L?-<WLGTj6YUK=sR]Sd>--r=*41YVGOTs[U^JK4n?dB_8]t1R.;cRG>"=$oGNO@c\b3D2K)WF>/V^q\Y\G[s*2MgVA#:hm.m?HhY")b?%X>0)+I&@*J.N<3)?$STRr)bAI@"rfGV>=`>ll*6f^k'achtLc^9l=_P)YG,=Rn\'i';_>g@Zl1)mMR&/H<MD1_]$OYXq\/hqMj.g7AD*U[?$nE:7OBBTq*`hNS#k;UEbmOQB2K^c9OOIO3tuhkr8%2GGZP<26A5l@GT^9j#ZOHWsAQ5:R&is'&,:]=Lbn5*4WPtd7D91#r$OQYeu,7JEYKp#<5H/[=0*emhu$Wr]ffgr`6*NV8nS5c3^,i4nt`N#g+omC@3eZFWk=T`gL`DB^=.W^hh+;pUWe3FVf`k,e4R:)'N"hGBI)WD1aRSG)q8jJ-rOSGX2$rB^6h2Q7Xp3'2eqUftZEor0A\`>$&1F[9LO%ItaUKdSV42n&ftPiphKMkOB7b_>b@L_'qon)?d5jUY?rKFW;`E;f(B9:ar-=EIc`U\QuU"XP91%kS9+V9(lD"a82.)WV#Iqct*&eZG]snS1^9,())-k]d7>O\_^O7fr^ea?2t<3XkIksX4jUe=.Nnu58PC$)u*c#^2Ma<37\:6UMom'>utUo6+`X=?Zb8B`di&*f[<,s:$(-`<4No75!V4qIfZq*61h5IpH;p5HW=kl@&:?Q*M.Q25=aH%dB)-MS*&Q.rBSAN!HMOmUdQe/PNgl6Z7-hoAlad#E(HfT]r)&7`r"QgC;1Wbe#-4]pec4%'"f#'a4OIQh!!SL@<HLeh$@R\^J(HM2a"]jbA(R*p:#L,*A/mU3At:>YC>$%3m,Wbo`n<6(af~>endstream
|
||||
endobj
|
||||
54 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 3011
|
||||
>>
|
||||
stream
|
||||
Gb"/)gN)%.&q0LUo\1cm@tjtTOEB#:33g/^:#^//c0iJ_b6:PCb`#(H]K"#>rdDDpRjabj[?e-Pl`#^Y&=5l\l%=V#(teU*1Om\[J2^DOIE$eWR,.mc/Jk9+i,j;7jhS18+Q+kSN&+7_?ua8R#76F^6e_s$@,Qa1`I1O4a"3]A!dc?*rV=6<JaJ+1/2s@0Y^AC:7u"?^S=grGq`FEP<^:/0'7I/?BFVm\<pq=C/^CkVJD$6,#M:bcai0%ql1*H1QQM:Ij:nbWB9'n"iT'1uo_?<SR!seSch0CW]<FDdho@$ukI8"@D7>u67*j%Y-&%*&]Z=V<F$-mijr.j%qs[W6\'[Mgo16#*/iL?W7sMQ>r_EHmZ@(fENWsCSeb%YZ?M3qpXs$CEPpI?>LmCG_?<_A.Lj%s1-aZ;"g$Y&uf62%eUZ\3`4s%5"+,0g4O8090bc]4Y=LH>[%=LQR2?SAF$-*RlGF7Kscpp@]>MHgX(1t"+2K+/kg(\MQF>LoDR4g7u<p5B*T]I>Cc1.5cZN,u<6i9r$oZ9`3E9dZ3ICePH)`V]*Adln:2GH/>XLJ5P"+S/e59HPrII;CEBg6PWL3<qDTA]:d#Iq1FNU\`kO2"b8<QEt'fY%L$lXAYP&,OeYgVD=c`un%I2W[r.ieUdl'dAufohdBl9#F!3M-T8W6M&b*:;mhu<A/-f[5Vr1[TB<%cai/HiCD7C2?"bZ0k^u4((0Vlb:q*"RqBhR,0sZMDKkotr.8spS\MWka0X"XQmE=8*eO8>@2AT-5n#8=Z+.%=/>l5NQX]$KT/maMR,EFYjDgEKo18$W(,CY3hP_1g:9t5J,aD4:$OqCC-fZHBCq\7B/T4\;]P_TrR*^p6REUZ&(Zb#(]=+s]?,bW]*[_ab0:&$;qXafUD[Eq7,[?V6]TKb1M!>\uAVt$]'d\Tq=:Z[S`N@;PR1nP1/.7G",`Ckm67Obt?V7m*;Q?h-lsugP;9`ofQc`<KY?1I\TiLCjcu0',$eGKq6f*/bN>93#F3A-ZpD>@I]==O]m5_'4+lcAAqDDZ:.ZnN4+%"RrZR6/9D>Y0=LfNL@[kU-mi-I:$$?'#*_'[a7_+]_1r2R:Z\)N:i1i9JpqTNZ(;aD;[i<09gMA[IOpF9n)H3@/@0O7%\REHGi10VIu`u6Se)[n;uEh1<_U#Tp72k/#t=n#J3.AhQf&t'Hc3(!9<K#&2?(:^IFR]+I&aGUB[Bh!=r<":C4@abV$;kfR67lZl.()cWi\B(CHB&DR\q@<OW1MSu"R>R;%e^Oieq)Q/Rf*.\2X'mb%,UJK][2PNqG7`cK"7OeSV1u/E,1+;mE(="fm*Ac[0b+"U9q74tMY]57WVl!smg6_IYtjaI(aS!uPLj^!chciqB(s08,0//N#h=[72"B@l<TY^Uk\hJ7^<Ij)^"+p$i*6ng+WuoV&Su#nOZYIP0n+e8-ciGS-Q^lOhD-a(7@$@\OF'Q#0$C=[08-53el.j-)i#lK;+fV`f6q.U(cuCUa\W&lU4Ek:Y$rLFBn[a#DJieh[s@A$^MhYNL`FTplptos9p*CL.V=4K+.Wr4dso&B53@W1o.cJV45!-Vl4<+)B7%88(MrrG2k=o2S'C&)ZZ3GDp?;eg%`[&-i^F\'34+o;n3_*1Et(VVC-[dbWatHD+1el%4Wc=b?=uj3B<4Cb=CWZ,0+[^nYN;0X8[<O9@If!V*\t-Ro$K;Z4K&lh0"l3#5%3a$M4I8@>9&Kid7()34*<]55HK@)[I%/!^:Cc;=;tQ5<'#DWL.-4qX)Abq&e?V4bq_SuN)pB$6&,s<c>SD/J!l<cRn0hWm].>H^q5d>hDD5SO5_Q_=!T9Y-l^4$b_+_H+8ugSe^cOi;WAhdQ?n:@$h^V=pFsU8-P&"WaMerN<NGDL6(*bbK.ACSNZaZD^Qo8?<hS`WV,j\W)jPQ/B9MP]OK3.($MI*^eA<GG,S63Zq[%qGcZP'k(#RL1J?1?uOo]!B]<?`YUX\(8nB`daleH)EFj?gkgo3uE(bC'+YG)/f%c5/N$!_Y7Tqm%$E*Ci1qpI'>C:3g*EK@^Cm[iF%Y%2OQZhTtEAse-DKXsUE)55sf4rtr0mkJNILuGaf\^EG!HAuqPpFL%-&nVgMm0M2c_CI4OMPE0l6=rfR/Fa5!#3WM;U77<*$lL[odRKRJUQKS?'\%bt]#BTMFb4(D4@n:S0:G??gXF8aY%('I4<RUsqgs(:Xc2Y*T^EC#fl`]G]ES$,g+E)oIVl:]ch6UBQQMm6C.LBERMuZ?+kYms(e4U[3u/Wel/bYfj+-N_H:bjUiL\j$[l[aRa(\-U2&(Y8SP?t?p'O._3M\l/4=e93H1khq5KL$;*t!%h?cB_^jI8j!.m+HFZHl^fF:H-o8PI@t%3-<QF!jgm;A:mtD8."Z\[sLp\WY(?*6T=B]_b'LV<dTlnL*U&QE0!NIS,O92jZaga`Dr-IOq3qrF6d$FK<*R5i5G25J`E$Z-!W\YHOb9^;)Wik0nbCls/#KaQn,>(HNTbL-<'>F0:^r;87F(EpKQ*W#X%L:qXn=1lla1O%8^:5=l:^!dJbcq_:6:ImP+R9Op0V-T%i7`MS5R:X)Bc.Tk6'pscaH?$.Y]26Bi:W`nYdD+43MXn^DS,38#iHtll&YA(tRVDW#q$TbmfA!Z>[Ed?Jq&"TQ!TtLtfe5Ui-CVM#j/[4X^g&mnXmtb8Al>;%3?tG\r/L>IUs1`)n<+"U.W0hXQ9?!FsH``YkoWRWf@>`a)W]@>XN8^h_rlA"u<tOJ>77D5q!(o-*XJkbfd"PSefGS&^?gq)i(TN/6G$V81?M$Gr:%C&>fmWfRd=P`aO!n.1+"*h=(M]=MOaP#'\uD-R#'R+T)QfI'hAIc22cX&Ud?=-S)Xb%kq/`RImM!&#U.*l]*']Z$s-&W6MlMg)L"CaPNcuj9qDb0uR=sg*3W03(NNI>Z+&^>;QcLj<#]sFo5dO%SS!(?Qb@$B;31_`_Sp=_%T[<bU`o5]2XImaHrL`o$_1)HN]Hm:~>endstream
|
||||
endobj
|
||||
55 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2295
|
||||
>>
|
||||
stream
|
||||
Gau`U=``=U&:XAWfS@aSktDp64?M!s2g.5r*9U5S(JQbD"Z]7*6KTG(;YRqM6')XhM3J_';eH^f944rAZ+Bua&H"nrJ"m1_04T7sM?7?CSccAP)oe4`0g<-2Z&%H;bQ`t\lF6js%'l@d'\@U2Z$b0"huo(Z4N@V\>(gl3B*an)G[aOBn:6($#AO7)B*Y5F3Gm?XlGpg<>lB37X#7;BT$ktE$6kbd[KM0#.!*5&`:A_F',N?KIsEq9HhHgegG:h(J59ekc5@,K?<iK9/t]uT$]KS:q'b[6jXC*LMX9J$R)Ol`q4Gj/>_obKE01r>=A64lDrO=)KJ=!Db&e]N6lO$QZ7W04'^e"qr2(ciLb,0;RnI-)dHh`ZNZS.j98M%`A>qsEU*ak'>6;E&,]!/G^iFjYfE\*_-Ca,\\OXKVI,"4D^fTJb4jJ:`F_)S9.k=g.IDEp?(4+2F\`\V2H'Uf"%g-"U/O<taX\?dmY3cgF*IM6V"J-j.>%rqD'n?(\_t@+/h=Ho$3"K9O75_3_6Qm.nUI=N,o,4ZA2=XBj`Rd;IUtTt8m((+kr8<b:k.!D(Ak[3k!gIm?2OdTLiV>C0#e_;k)S!3-7Y2#VMYIA?iF14tCIL_9niDUPWt9!4(AW^RgYV[Uqc^rS>;TNi<Mj'1R2Z5fRY0R/fp.#b(6&6Qdhu,:e,i]Img)5s?!VU<7/:":Mc,69E^8psqLe)cYiuB$B^Gt)8#Y!c</@_HV\Fk00RgACa&[5rEg=57YG]Wl."rR`;r_bq7=>qS\;^"\mNG3^1leGq]TX3oj^q606mS'e/CQIj?7$!62d274E[e:oSBIu=Z:=.T0Qr'$NY1ZN-kg#4B%+!=9*u&N#Un[9k-]05/ACd$iZ&1/n&"*I/4,^1LpB"Y[-L_kGDG8:nF)\W<QV82=)B^X!jq``^ReOIm[CenW%nA`')9sJ_`XW,3,!!$-BX1;MV^4)9[e!c_/b/p.Y<dZMgM[)F5Kl-;t,Qti"AQ37'te2hVAk3TTu#Xc@D;7Rb6l*7_"W!F-HXtYi!NO9Yk(GHFm`2mdT8F8fbOF$7"(n<P^L%Y=0*'"O"$*`NF%2m9&,rj&kTRWuf_mhVr\F[>E=QHUj9Bg]quW4%`N>/R4bXgj+)tqC$58+_;91\QC0[S",m7)^8jGYi&escSU^D;W)G\a/OSo!2^m?)j(-U2-@X,m"ue,<?]Er67<Y0rM1')W6?V]gi"BDMF^I5@gcjjmdm=8Zf(>1cS5]O8j7D?ok31Ms%X*+qC[9%@$@5-No[\"7VqW?jLD@G\pb_cML#%HrDY?AnKZ>!r3lO:_/@H,Hamp<Hdd8$l-NNts5t0"1TRmg(m`i`R^@=?7J!maZ)TmA!M1m1B01G1q_u3:J^Na[SO$DuZTV$O5>Y;JqA_;[a&7&qCSem3a4it(a1;kTDYp'<.`\=Ad3bm=@J^"=c)Wd8mK!q)lb/;*:Q2@A%>=&>fOsB'VLVL[D&F[tK(VFQ]Rc`@GH&Zt$e\8X)Vpu%JRd"jSHsTk`Bs7OZ@&@K:t-f2;Pr`rK<BlZ,<9SP&U,?kC'EV_6qeD+EP3$Yn)#:!LbmG-I)o.)Tss6;9Kn+UQ(ZT(%j]YK#!-Ui,Oub7?733-5GaRCa+q5/(n49F'rOosKNMb3,C,j!2sP/*jg[TUIS-[#e*ao!2AZA599X":W&T0tNDem@k:WO`'>rt:JbI)VRHhD97,Gjr].lS?C:)-r<1?A9QQgB!foHV-o%;X+B/ZK[hsG3I:O-G>3??3.VtpJ]'^jr0"T>=6&$M%g+`8's.kO!rW9]\rJXi>K@8V3Wjd"HL_Of9CPRQXG1DRRdo%,p]EHILSo\[PT)MO)/rN(#T'!p=J?K&E7d/b+5"f65_VWNnJW#FKjpU&$:_*Ge@TbDTUeF_Q!#6RD@[%-mJ!"X(rg[!&!c?V)#^OdF=a'<57YZTf_@Qn.Q8N]p+^6BMD6T=!Tr&'ndrh8uA+*r#)qFpNP?dDQDBc?,2>Wk4CqL+HX(^fU>,3AR8c=+toqZ58VF6a8M]\$VGR(]ArSCS-eLtuot"$aa"l<;=uK^#-_9KcYg>+%[fe\ksfYJdn;1WD[D:Lt.#;uL5Ip6Wid?B=fVNhO`i=3<-tAubR`@&=E)9i!tkV)jO9i`oc0Y=+n]$\uah44D7/mn;r'\pcWUOa0ZAS#7;b1/s`#<#ce@qWY!Pp8"jWCFUK^WpYp*<K.D%!NN+Y1/n\ARYi=W`PZCa`DJu[7^Qtc-F><X#.4%fr8gU8#Nu\5[!VB?m;+q`rCkd=p#?LOr!/!+nkT~>endstream
|
||||
endobj
|
||||
56 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2744
|
||||
>>
|
||||
stream
|
||||
Gb"/)D3*F0')oV[K&>-2>(nUR,o*ol[Rmp;hDWW:YP6"d6-J&;Eg#YM-+OESj)6><ZFmX123tka4'[Z?/4FSkSfrm,B1rkkIn9<X=s3J/*5/j/LeoqC73i:.1qu>[k80aC!mK\lL+"1/E06fDU?_0@KR$FHK3/D2GesP$EIg(U0P(;T.!5@b76k>+&rM8p0b"gb@=fNKaY6MX57riejpqf.1T+;/1)e0-K$[/:>EU8)KF/D,*(2c@JH_LU]t'S=?2sp'[k%+92>V:"@#RG4\(>Q\=#]$1q+!:A^0ep^/coqAO$Mdroe?n(%dj+rFP7M1Cc$HD2jJ1pN0Ck)eS%6_KO3s72I!4;c"+OAm'nK<E%lamP!;oG03H:PU&h^G;bX`=rN4X!.iVKR)F+F"Z:7Um*d8gb_U"B1`#OOCo%l?NS.<V'b'c)VK.'<#Zi*G5.-mu&X<USTji!g9YVV&k"EWmrLrBBB8H_*n53))&<\hMIhPT>Y-K>sLm0pJC0U&1Pafr<O/3bj_+DOU72?*fj^Y@.#L@;hRc:o]p(_csgKi&gFdL4a.gEH1JRNb)bEA]LaF1G2Zr!CJ,<NbF]I5\^ChSJR7!U19i_@aK!")p5N2kUI($+QQ!?4O6fG#)7uc&jJ:N"UKK#WE(q;%3%/2#i/>0BHB3>1NQUccUD(3ErsJ8#>P]$ICH+1;aH%_?Y/E'[eRrD5.KlA>uqA)dS#MmOC5cN]Rhg"l6/\.QF-O*68-YNaU:)UK*WCC5l+s2O2RI,3U7"6I;j[d%-Gjl;]-'k`',WLZr2`6j(9"(pQV*Ohr+9M+6qH&Ool]#Dp=siu%_##htn=%-YTARY:Y&,]nMXULtc[!J`!m+qR0#^EAg3E<=(Dh$L^ZEI-bnYX)R\&U`G],YhV1&J:YG*X$60H3!hflXB3b00[i*_Wd'L>\Djd`IidcHsi>3hQWa'MP;g8Rf*`t1F+5(7iLA%"KpCsL>>r35S'P2JfC"o248=8<M$&'M9n*dQr.<mh$l5bH"5mZ<64U>MItN[F`P]Fd1)*eU-T&DAIS)</tS0Z_/K@"<%#O0=p9QrpR!fNJH6R!YMCFS>hE9.l9/q;Cjf7kmDQrV(`jS]-kPOp!rF0Whk$3P"<LquSZ'<9644RVdc;ooN@aKMfQlfip-t=QCmOt^+1m-Pm*-J.pk*H[$pK_7JcBq"rM/5j<4N!AOXIs;QVpginK(YV@_A,uK_HH=nr7B'$!Y)ji-m&j'Drfj:t3;jc\0E0+O#@9`+79hs37A*'B*QMmYO(,l'`\)]Tm3^j_-`thI9NoH&bjT<k84ZW_D$&mi5(G#sU(KVYdi_28.*ia1ZC&Sqq[q_F@4KV_.9FkJWi8TdRK+%EMFSU2T79_"Q31C_V1^[VfX)*l#KPCq%8&JZTZ/%_@c$qE`^?K.uj*1*N&u?Z#[@jpaFG^;8Qu9]8:g%k(tRb/slOB>.7^\Bct`EJ:l(5O.s`F&4:S=0ZJR\m<7RYP\SGCn(SEpENG5$Y7c_W_;F`2N2tQb&(1(FU=hhkn6HJ\`>e%/7SjeD*s\%Ab-T#Z2$0o\$!n#KQrAPKjM[-Ken*="m_i<$@E93lCX%T@tK-JrCs,'90.7$?^H%=W_89I>2@=Z#LKBD*,m:XdfHCHg/\po<*lo(6,0<lK&X_4Dr)FJ$LNsQ#VKaW28RN<#?%*f9h"(+&8\AjnH:QP$QlS+Z7Qb]>0I9OJN?-#!F,>Fhu4d??1#X\#W"][^Ms5m`h%^gXJ-B]d)V>mV0/qOksRB#ok81lfVa4fZD%.pksiN"2qQ1fFsiR8$#bX'Q!E0X.UTeD:[7!!qQ\W5b5H5Kol,=n8Eq+iD;FD"qeG'gXKoEI]&cOcCWJo%(R@p<71;sOB4]n"-F[8uE`k"8J[5_*cOl_]4]&s;D1Zg\%550HH`;I/AI/Z+j=[(:aIVRJU$PkUpZ$t,DK^0,NXRdMX]=ig0,FXV?Ujn+kVueqb0c!,U[NQ'2>0@Ch"O-Nlil;>^!I[E!P5u]>!oLPenqU)NP,mAqd*rq^#6$,(Kt<%m-;;$>%95J6SGg$hKU_+hHD61_o1-/E%if4=>_#)[:ukdg&mB41)<^Q#Lb^rGj(QUZ!V]f*\EC,+l($k(B2)BBG,Rr67/I4G3R8&5^XR1n3m)7&\mEt_?l9?q%8dq$m$Y)f3`+99HaG;ADWXnot`\!;e:/ll(L9l9HjZ#Y^t&_InN;a1*jLpouIrj!.ANhTr,FY%9q3XPi)dGDt9taJI%!CA*SXQ':&,8gZ3;q=R+pg0p5r<i*mtN6TOF>0p4qQ/^6+Wh]H)sj7fTm]Bca![C0$"OhUb5nGcU1Nl9mj#<ljAme#2(][;Edg;IWPDUdrcZOnC/OX64fS."\Fr21<e7b+(In^&(pZ<nlWqRaT*!/`r$emhk4N@K>lc@Z[8UrU[6n?B-VjYhmKLUd/r%D:E+N&r&83;M4F([]WJCZB"ii\O^JEY.=/6$!?Q#I1Vq$5iOO?S'8ELfRY'4"bKSlA_g?Mt%IpITms'h3O&0@.N4#6`mB>8a1M1T%7&8)W[79!DcKWSHGlN.dE;!9`Tag*0Ol([%E%_1?UZI#d.K)VK6_L7Q9#Fg)17E/sp$("DE_d1XJd/DMg^&>G[XTe;^^DDJ=ckT1%<NbG6WZ\2:s<2*WX_hMlbGW?<$BP#X:+Yb$o4-JT%P\Jm`kDV3%dYUt'/*`@D>$(m^70sArQ/8"YORr-K9^DV>L]7;O5%7lQTFT~>endstream
|
||||
endobj
|
||||
57 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 3055
|
||||
>>
|
||||
stream
|
||||
Gb"/)=d.Su&q6H[J]C?*9G7D%=7*o,4d5HV=(^O[5,_)-+X%^X8^KRHfrb5CgAlIh)@7>$fXGuQ(=FK*C\3>K7PODl+0>Lf8qd2P5VI%1_?;IA#R=[d(@TVpio8^HkD)Vq!p!dA_^Iqp6-\PdK(%^KpQ?rA-qm!h`*n<cm.l<(n-2DK+"oaHpbX;<$W=At?6#cTj`<P5(TBVR]MI'"j<a)\6,ZksQqRP_'b>X_;ib<!T`k7!lmM:3M(V$+]3\b"I_:#WNS`H`qs,Q)";^HE4tNkorI>ZuTuE&p+2pD66H).naQ*2HiXnC/r85Ss06u)>FOPCBl]sBG)j*_!,U>j_A[N:6Y;3qOVdRQS6DG)M^Uo\:H.+B[5U@VGDqG_qKP'nfPt4jpD^l&c"A)!/[fDI.d@\rb;_La05BrLeVO+4,6$(pp5*?%TS>M-`59*TgorduUE2%Qe[@psC1WL#9i^G\!5SE@93/tbJNSjTJbenbmn/DP9+-#:?LnZ$ad0m1VOW'`s3T0Co`oaO0fUgR6f@).)5ZBCr!=6RrT[IfU^^gZ4I#),BlodMJDNauA[t(!h%Goifhq+<fNJ;kP(<D^S7b,BY*!E/S*bhU0@UTh4rP5Z>%&W^W,bQ!E1J*"b7K`&a&YVJ/A-mk+&=;?,a^s9m'I*/"U1-1#<XmAcPIEW&ZL'+!>9JT]Fqg-ld,;oMTV^9C#MhKGpGI37]+li-?Zelt0\lcJ9)/GiHh:aue:cgF+>n,#M3JS\E_I^`jX'W(aRY+]LdWCqIm=DEKo*H5`A)0d2I#!-`05fCZq%3+J%GGpF*G=-H1MB2XeHU[.mt\heXH)<qe19Na9pUn!chZCXV\N9q367Z+XaO`pDpSrSY1(dq2k$(%^mp-gihnem9%uL840@P,4iX5</*"iI'B&XPj>r_fC;(c4,G^qP+H;@HH]bSYB!KWd!nm:7';$.%O)!UnrYkVcXE))[.'gJl?U;!I?jEZ@[\]fA/_lWl.CkXWoJ+'ip@V?7R"HnB+sVPX7O:-\,,ajB<.:e&\lL]4a=-[8l=Th9)#t=:@`=2&D;l!W+:r+b2bq-.X2X`Ma;s5;$msO6q^CUo&Y,A<;N-t^I,bQ$!>'-/sVHLfTD1Y/87d#BgOH3.LNuO=UV2YkA/Wbh(+/\q1uuWFhHEbZELBfSmhYcNXRe+)^U"5r:Z-D@CBnHRa$-_SbFE[H!AFRW/G1jRW5.mS/8iuq23kOjiBks(U1f;Q$)h>X1gr&GseaH(M8/olEC&Y5_"J5^(F349>im]GY#l)LOXfc:u.G?bB95LW^%fTh+nh.7DDWPD5jR%[49P:N=%Q15_[`ZKoBl)YS_(NXr8307N)u?L'EQu"pLs!_.'59"3K./d)YTR!C)*Q0f=RNdX4EM"<Y+Xs0VlQ]$4L]IbFHA5;ZYSR*=C,i>8/bS(j->Qe7t(d8b&^(jnoK>p7Opi-4I%U:b@4Qu>bY#jL?]a;dO#BWkFR"RWf/p+U*5qnAB@ke_Okbo8&NM5oL]*LqPf(AZG>n$q4!Hr09A=,;9S[jsSh#RAc0"bd2..N7\3N$k'o!*!i>`8hIo31\@T7tZ3jG52aM4FSObD"<_Xbl&0c0>K2)Iin)X`uIN+q(PCcP7RQ/`\.\E1e4jlmF_LL*sU5>j!;J"!(s\?^PSTC7p>EFf\Ss`>X5s`r$Z><M>8s/5<cHeH"<qDe]"\nU;^Ve&i0UYkV,%)<!ts`!!_HaR;@ibgA[ej)Gt]F<gJ%iE.rMl[\1=:DjW_5)0EQjDgRbd)Bf\loGt-&V;TmmFX"sm5Mj'TCu2,!lJq%.G\*iV'1>dc!4a`7$"^`bq#i[_Kat(MFi0JC8]4KHRLQ_VR)\B9nBcJ&^c+ffVQiT"-ARKPAbSX<2k^(pBa,e8gV*m$(ThLi\dtbbh65C7\KF+F""/?&!u)AJ<qL.\PJ`7&99c\$MLT1\].'7@nLL:,Ku'5_4lO[:9+o\3_="erUp`Ni`'m([1@GTIeR-<u*!g^D3IuXoU_*ba#a\f'[B,Uh*k>qre6UDB8T)Ik/M$$V$B@"RK1:PPWqKp?Dd1sn5^aI%U6;'LMi=X1?j.!SJ,FQ?H?TF:/&3ZDO!D.pq4\G5T^7S3]Z=3_Et*4Vo1fG!.@DTecp,7KY._"(.s1gjhE's.>pLYY&6GTP'\s'[GD#Zc<*sc8_>6p@:cV'*_)6#X,U-18=QE<D-IJ4fl1!B1OC*8:Oj=!*75G`B5Uf)-b!:#jFkO7PQ@&s\&ULc83qeiB)@dq;g1G_`/*'osI5,"Kpj-*5QD6t6lelISMJ>#3;`?W.;o4M:f6&;[PtRd=(D9HO&2r?S(W#hge<G$\b#J4IfdX$b_:5HMF]'`=CP]&*rYPucV)sh"dnlH<TsaI%Z)tc-V7IH2A8]Tg3,t&p<6l_n'D]ZXe8'3]+5fJ'oT33#lg;0]'0,B!9(+`_P(E%pp$"lr$Rh%s)NIoZpK4]#)j,\):<?"c^S,t.F-5Y:nM^q_`1X&4BTB*OY>09JD@ds&*jsT+0EO0ZW=FfOWT8,pbJj*I[lA2_Tg/c'@,MaaW^kY@o,44)WMcGVot&jAZ["R0Q)R8Vea-YK5EV,a@,CFB[?1@3jZF@E,3IQEiC&8nHEJ)Od^e'e_\Ln?XL>&lERCsT6`B.<X4Ds#?275,mq8OsY!7kL62`BFm>C#%W3#iVcTDSO<Dge0J0/DJWdS5*hJFPa]Hec>`((r72'iDY;RRX/[(P]I/"RRi\U]>(E,3%eXmu4h5*Y4jOaT:O6Ai_u?LfUGkh(Zs=Q]Im.XBS!;NH_QK\[#,KI7Cm$`)MX#usgi^u6ZpQ\EZAa6r1E*;qH'=.ATg?[;H7+ee*8qXg32h>A?+hfV5_mNI=31K[A<Bm@:e;QGq[mMH@4XX7d:E_rfn8k';*2-n;"j)(Ak:"4.CTN2_kNZ?P`UA>N.J**DYqr@Y0bX'MG#:ch;CIU'b1X<Ug7^g^gQ&_KcjOEjr]'U@L(?)s-_O7]X,redc@^X.i@\W4nfR%P&li/k*DF5B\D5lDqK+s*0Wd[I<\KaKYrrj\E4UV~>endstream
|
||||
endobj
|
||||
58 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 796
|
||||
>>
|
||||
stream
|
||||
Gatm8gJ6Kg&:O:Sb[ZO[AS65#QcBf'8emQlDP&+Q-?bK50@Y1'pUe&OO?a4+D6.8<j-auNbfg9Bg'@TF\cDL>_=8-=)+5E/5m0SgY_[m<!MQ4<9E7A5Teo"Oe@._Xo9>q:98O4KO>%DA#a%s'V2\#SEtf28PT'fp(hWhG_U)B4EQF@3UMN&:+!B;!oHbtgTcOTK?L5':6PqQb)C\N_gV<l&hp1B3I!K^S5)7Sj>gHKA1M`L6^.._#%hRP_FO]>1F@efSY:Scl5%TCEdG^]ulYVcMD$>ipXlFTD6bL[p's&6NE)Msj/4n$-(?[?mVPS5L>4MJ^I\_+midt7I#jpOfART5"D^riN2Qb0F2UPQg[FGcGoFU7cqmdkJLK+3!oZ#fDh0/4Y`lGG/ar'Ns%i&a4X7ki*Ga1@B0(!6\M2DnePm1q^$.105d')l>j<-br+5'Jph0ZVX]eJ9U[:f6"HC?2b8GsQ;iscmkE/>V0#e2,*,!/*294e8H!'5X5']5^3]VfRI)iAj50!d=YZQr;o8S$H2G9C8J1hln"gI5(j)(4s^Rp%`WAgeS0+M?S:6'W5`"eY#RYikTeT&&PZ9q;57X#=1[c)Z9h07<AHXJm+/@^sbE2VqDU0g-Q/6hPZ!-kESS<!=OM;e/T9kOp)IfgD'AXa+:("8cohCi.A#c42`1MuDK9mLRiT4^SWO;]mOr=.]T3iGhuh_QUI,Ek%_5dG@=*a567)iV%ABC[@ndH)PqGBKc!o`")_t@b)27J:'0KYVm2$S3'4ga3Zp(ffLkc"2SoMig'9~>endstream
|
||||
endobj
|
||||
59 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2779
|
||||
>>
|
||||
stream
|
||||
Gb!l!>B?Su(4Q"]d%`O"+hE2S8+4C&Q*&/?0"lP!W\,1=D)-W&#jceOJ,C?Z5W@L.DEO`;47UtPC8#^/cDU+Nh&m!@5@XXg$AADm69i+/LetJ4+Q@`3>.m;4X/k-]4<-a$ooV`m$$_CkMQ6&D$$'I5]ng`]JiN75(iU0#%o^KAKg_F!Xum>*(/s;iYpgeKoOC!,:-(CQIR7_obRa7g,L^fj1d-fo%RMa]I>MHp'$='>YSZ:))A_Q5rO\QLc.cShrd9?I]LAH14u5[%nFQ%^<]BLHL/u'_eT\,:URca_d3!t^EW)8N'`1Z*O"+iN=k3r9lFIW$B,6SGQ72BndLa)hjX*W3R:IL+2)ak0irM,(o]0(Vl-IRd$;h<qob>ljQ2[m#b0!mgV#h4%-X!P]%#tiBhS[ZKO%06E0,hO<EA%gCIF[i7aS3P2NXYkWa=+B$[shL6%ikEGbj5I"l>[S/-tL7(8/q])i@232pP'!_e>>C68KS!B\o))lR"QF`er'VOEThZVW3N#eR7,CnoK@R]7kZd:Ol%&o`Uj-9N2UaPBT0#ZC_U1h3q2#Mn5&iVm0\pbBb19m@H93GrT4qhki4n1^pV4co7cEmI-0Wk48-)e%k@(_Kme+moV&bKdF>%Sd4@"k.WA-[1Rd5:0\:Pl>;h1)'!8I1dZ2Kmj[N8^8(<2$KM\qsAM$ZI5OJrrJW%j9;D7B^!:5'>5ac.mBc1CnEs%S$@(6Y:SaQt8BM"21GL,L9-91brWRZbdf!=*jd'FD[=l9J>Yf8<=&.8L>;D8a(B_@1aImH-+Q\@](ggm6Ld_bT^l$?RU:ESGeSYu[k<=3-MD^#Mj>G;Js+E@XJO4$]LQ)p8Ecqdj>8H9_&`1mA'JUb\h(,S=sY\@TOAZ83H1;C]iL,;fF&-NM!8#\h!U?kdHQ3B&>V;,esI=R8EEY-gK1Ta(PNH[-"R_@:?Y/)RU%"XJEKdih8p3Zt^%`"\KjNg;g%2+O8"?=(G^^nGO]9mH+4!c7bNm>@u'VGhg%R@i)/n@cAdR=NeDG!*K(;T^/A'(mabHp:#d,6`C$ZFOl:d<kk6^RST:B]SmlJ3_I:GM>-KqP>'5i:%.XrsTcqmjd1`,i8#"R%r%^'SL-DBT1",8ZdrfQNAo_F3i:O]ZjN/a>g/jb&uJf4$8RoJH%/o+b])iIQ/iFMfn2pqp>(-9SUc$7N.'jD!MIa3Y?Uhrrcs8R98V5,2n(^JDpN;WQM\7KrV(&@bO-E$OpW201#ULjYVORL-QS\P^C5)bm2ZKr>V]NDEa<q&#D&Nu_R7j:b`WS>8j/n[-h*E=W:p`rQ4)'JLuMKX*.LP\&7CUhh?@VQ+$X.G+7A:<mH%M9jRl1eFD,7k)46:ZpCb+a03c:p<c-IK'AZ-Rl'*0n[]79[`-.'l2:I<'9EKU+V@-$F8kulLmp^WQWSj\M/p:2iqci=RZ2+M0<?M[=e_+80470DQ\G,A>W[M+q*oeI"5qKnf]-ZOq.eC9`C0q?(ItcAJpKTF1dm/h:@;u?uQ#YB,9fIPKWP9Cq:K#hhaArri_5RCRNJ9Pj<[!M&e.If?SqWl0%Wm]eW7SV_q:4q=7Ou"u\Jj6K'q2pIu-Ch,^+Z3>R&G=Ut.E_B/]XJ`!J"?cM;t.nVKAZZa66+e_f5G>b&+_65*&I1p.)G8G/*K8`Z=Il\;e_`Z.L\aMq]BC7:)]7eeUT`+$Qs-Z&g`<:6l,*lLiSKH:rZ=h1koNkOlb"PUslLcKgDh/J!XCYCBV+fL19]euC/e`#U)?tBY)VPIR8rh+e'7sdogrX<>==S&G>=cXu.[fb[SPm!4jf>-PkQ.@FoBGjHT14=ho@5e"1q.^4=_i'o^&m#sa54;?2q`i)@D+9N'$)-4?@M>-;u\E8h:5M9O(J.d5\fAgNkn''&+E[n@K;sGbe(KsQq*YbnsPCfEdXWN,CUmlcpp*fWK6k=qJ\8e+,Q!u)X%9FB'NmH%Lr*ij6f.,00Jr@TIuTn)TU^YQrFo:aj^"B4Pccb$"P@M@+",M;0c*#Y9c`X?DRPOp-S&XRAB+!<ThZVqWNpl]^%D(GXZ?j\_^>$PN>\h'r9n^LR,3CKn/iD$?Iot0<->bojB^3c&;h`GH.ZOj4@u=c#*0P+Q,%o'W3)c8YR-ecfCh96Eu@&9dSoX9`mn07KMTrccS&q<!U/7A:ZqS&^]C$XA#Nt?H1cWL[.>kWX?sgpJkfaDGRV4<LY>]iL=_.Z?jU!3d4*B,=@&/=Q,!'HYs?T%F-.5Dp6K?SG]?#D<WCN"ao*kM1C,eLWuQmXR*TGG2q4@5b`Kp#eFM)SZ$0F@i<1%cjq@1b#c;F?918n\nKlc0ti3fWT4"RA?#O+C?Ptge:XjN`afC">`_VM]TSb]9]^N7![qlA%&KFTSQ)#:`QJ6a`9\NVO[(2%,cU+;6UE`c1pBLjU*BWiA^rUcA;M9a"NA)@p+U_km0kP((q/%Bi2F2`S+&1:,l?[)S:_:7<fY4lEGZS/'3!hc=[-"4na(0#G2c(NQY$1c/>"2=T%XiAZR+$qHar>n(:@5B<93c'VWm4015er-h4'u_f9J&Hj$k9#6u"GLbI*XYD-k6Q@(6TT#*5[1?,CQPD=7q5;c16pqM99\jIi`]RiYMnc8j+OeCC!c?d!FjbP,WnB#iTU6E.0-;-/\MfC*<1l"WqlY73O.VUX5'cC-3T[CEPT8mQ;d)%62IK$I>7i)f@_+.i$+h'l/"[\haA\%k8nI,fkR#Pp@Hktog'PA$",mFr&6r[#UAKRlS-I!,e*_]Epm5H1is.0~>endstream
|
||||
endobj
|
||||
60 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 3061
|
||||
>>
|
||||
stream
|
||||
Gb!Smfl#S)(4GpYn?LE4.&(kgP6'HV-V2/.m-4cZ.%ii+k_bOd<70]9qWm<L;PGG7(0<us1>0lJjQ5@6k:S#?ZgjNcJ!5hrhra;$/'bZBk')c2QH/,hluIRKous`?-uqF^EJOEWbUeO7la%T&_Uu;k+Q1J)IEMrqJGJ!+*B1b>amOU<ZZp#mZ,8+GV?X@n0=DBbkM<e$IYAX-dP#3$1oH$E'#M]i6n`KQ'$$<USUa/a[R*'m^l6N%>b03e?bO4'Hd7)TA2iZZf6,GbHhhQ.IeAB=5I:/=XW3KF=?ah;Z/gB0[UV>+Vrdqb,Y]WW\mUk)?&EseqL"h\"^<r]a=`AJ/mZR]8]Bi4`V+5h%=Du@]T^*@=Y3n)/XU&$>.e?,^2EmP'eIUF=lomP#UF+i$l7W:7(ZO8)d>VhgYS^,B*Ojid+j+^4t#p&0TIBP*tMYt<P-iZjD($6%=J@8]8<4k"`>0^d"_4<*.lQ4C9+Q:_VA2AFptXdE<.-E.9=[1!-YbZ5DT@@O.eD^n$^Fb?t@UQ6gsWo.g4(3(YGe:JKAXid*>3lU]fBsE5p_]p9au$^u*IN:itpr4qo#,T$)[HYgHq3YKpU@D]SV3i8a!Th6a%\4nbhlmRO3o_atrp\fFFDo[k*.7[nF8]dj>hV/:do[&mD(%T8&i0O@fHqSS$Lo+a;kplSr7akk,srAR:UQ5`lNIM7!)r1K7/(qK$LY79L<$4T)4a_se$'S7$%A"1L>l-A<s!U6acM$mcBWnTuN]B">45lQ#o5"1`ql&eAqDiCaaPEB>qE\'^(@$]PnNCPmKTgq"fc`4@PAL:MX#q!F=19],Q6#fCMV#o(\.qj)b;X&t2Wn4)[q09kr/hd4:IP_!?,2oR]mk1fuR0LNeD+,m7Q.cSBA_Z#3Htpt=a2GE$2PO`OTmcLg5"UlR2tB$qZ8H1SF]pOt(Km3&/$Y6QRhmSPYr7[iT0t$is7bc&ficjA`N?)7<Gn#>D".X_B6Rp<rT5VGLEeo8L*$h`I6o@Oh3K.VG(%Qo'FS:h-hMe(6@f6*I`@r!7=kOX!q"bpqB+te)39Yb\5I#8#_!t0GTD=MB:a%JB7YPI0L?TEf&4\f5rgOOnoXg)_0&u]5(D[;<71>\XU"r3PN*X[dBk.AMXkPHf!90cZ$F1*:B$?0+LFh2>%GPm"u^:/RAEp6%UTtGe,-i<#INIb0in`9$sAm*Q)4<9a]"L@'U[TR%GJHg415m#`SOQbQYI91:P"6;^e;JKZIgR3IVg@BD+ZKG"hZREg%EmTKu>JVCa@hRO]UO#h.W\SND+[DPTOj3g>)inpZGY,2#XeOG5iq[7bTteDdB6u>aM3I1;u\=9$=CMJZ<^IG^N!M;8_bKG?7R.!I[Eq4-8GmE?9^dE^lsJ7S_-ocUa_o1]Ffd;N-^sODe(._&q5G[.\a]8=nSXF/-dt6Xk<p@3j1hSJ1,$+?K9W]B&K\jmc+&DL;_=-])mdWoqciJr(&6XDJO8-DiE>pm@D(3;YKFETAL#^K2TB:Ahe,ZHI_-pA<T:QM7*afno6t]k_%&]5rb@jiVgbDd:I8V9b<&MHVWr9q&!%&r?^E_IBrZjsXpZ:C\l%2&4mk!MHs[J`$S1S`thW9@W2A$;b\<\_O*.JgtDp4&"$Nl:,S<i"tcsST>9Ea,U#G+N)$>5X*6A1@S+3'e`sq*r`>I>`1"ofe(L,9P1BJ7/n"`==&u+'nEthF=`Rn74RY/27F;3DsOdOQ`/oL,op!NaUPhiCRM>c&RZMrkCa;`7[<+fWY?egPX&&mLp\_6H^cC)LbG`L@#LG-BiR$&<U;#d+%33smnR?l^B@OPX;9o$ecso=VJ5q?re*%=MdsVFPXFs,Zms5;'2sY0DOPE>esKgGMMgARX&r`rYGRGkVf=U/?MXEu%CR4<PKW8eK<kHEY+@D%"8PZN4&1ZiJA,ln?s9h.RUSet`>5KF2T=C#KnGBY,k"$Yl6$li!nq[G:0W8"eXQbg%m@L&U-B9sE'eFPJ\r#BYj$\tYoE_]Cp8[:36B@gL`\c;>2Al\3Qd%i5[>d7UC\SORbtTC(We&QGL$F4FmG%2N\b&]GZVjf7JIl+I+mndK0)K1m!<Bg+lbV2''&7^Ig7:"CX?WAPG2;6mtu-g8LV%Q!Oc0tFj.MI^et>$;VWHF;G%Ygl@T\A[MgX$!Tdk#/a-s$Te.ApFQ^hZ]0X,P,lUSmpoPqX_;OVu\U%DF-jsSs;64?C&t/fVm9MKB3h\ZD@ii1EAGW/UQ?s'8$/Y`"9homn#L,@CZMF0_f<6j2gPY5=2AFae?POf)g9Pg[cSEp.]E]D1Ujo.^d*2Dea7ek*Ld.Qf/6+ZZ@;l6a;jJ$sBj!>5A5^S=#u8>:gc)%7[O"BO#)jFo59F@SUu"mM8l^(\/\V4Y`2TW6QhSe("K[M718i(*('!,)i`MkG#i^PM#)X+[oPk)WAOV@S_2p^m0Xt$EgqqBm'k,+(%.0*OFG_4=LH.+!jK93HqNBaFC"X_V$J=WQCQP*U$:-t`D-;BHQ!gcn.oI49gAY=lZ"Z<s-[/F2bTl\U;WW+;:oS)8DO1F50C,uMY)XM*Va8pGGuGH%&bN4latM9#ddRfJ;.'B?V?0:[([qY=*hQR2mlW5;CK",h[=e7B%]:X5*AjU:,)h.sg*"mACaW/)?+l$7[Y@l2+19*CF&%AW&thkXEL<i8O@e.U_p`\F",d5MGfmtf#"L`8!gH;-L%k(jMne;?7mk]q%'W+F3]E3>orrqE"_TJ+E0RcqlQ)`Vr=?N7j(MU;]!:3-?J(WD!Cc4HW?5'B9ljSb2X3bdr,/(R6TZ2jPif9rktj]$HG,ct"B@?>nY/WN2KZEn+1g2NZVGSJYQi+MPZ2kWVLGIun!?#0'.<u)Fg+Q\Rq\5Sm"q=@X[VqJqsJsF[+tB.QS+mdFWX5Xm\,tbQut'i9Wo?"%`ZraZ0nHSl1LUM(G4+Z]ri0RY*YW_Hn^kUZ$\5@n9$)s#+'luI_2I0NQAbQdoCe-?*qW?g3o$R3$)]m#;t(i0KgWmc9>M5&%^nZFbGX$p,dDBq_lasrTf4SG4m!o+QDb~>endstream
|
||||
endobj
|
||||
61 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1620
|
||||
>>
|
||||
stream
|
||||
Gb!Sk>Ar7S'Roe[367:f1^l%3/d:t,[Ea:G?.Qs[P6$6"BMX=.3hi-RrUi8%bu_GiQ3"l9,U+Z-SF9^t,6ej3=oCS,!IgWVeMRHK@C?0u&J&J40V+1E7YJo<j;nlPRIh^H*6RBA^jA@88a-$[A0_HlU^rV#b(Ym(1b;so#RdVf3"R``$Qt!:d50BWGL(It!M1+q#>"(HZk*=p0^Ji"/o,<\,/P5l+_6f?1rP#,N.8U1bU1rNG981$IL>h5\I]_s+/Zft-9&,9p'#'3><,Hi:Sa0rTZU^=PS6g*7fIauCiWf\#,\gY<AbSU/[NC2SFZa;R:;;T\k"Ta3`\.TMXVo1^Mh4A?.ujYKE4h],G%gdS(/V35ZUjq/.=QOO^sa1;)fO`\okcDL`]7S4Xk^UiVklamN7.DLE6br.!4^o!l7<BF^lf&XkYYp,A(P31#Ttd5mY2h7'\5VKKbm7N&'lJKhL8G`l*)CfB/pe]G4jf:ZR9pU**=kHND#3$5TrA7!N[Bf!r.81KkS0mn9Y_*^V7nYW[LWJ"U?8JZERH#FtkW"H9NC"_L%sB>U6-G$Vt\G`M,pGhAYqlJ%t>_4Ci,p9W<2U[!Ct;IZ?@.SJ9a.d,A@PWYZk_@.)p6?tBn4`R9dq2K:ClE396:.hW<*S\hbD9=?:g=jaboC4lEFJ4RAc8l6koF9_r[>ORWfMpSM?_-lZbM[_KAdTNX`\Wn*&LX`LgJ9]*"Q((nIC.%.$<j045.4):=JZ9>9X4H[$sja[QbbbQh7NoGp#n2,U%X_U"?rebQ7=<-9#FG/W'.pPEG!UZiUT`u=VYqV<uOH><`Ol+fnHqA```//=9^G;)JmgS!co9t$'#;\\_NDRC0[cl9b(s(3'b(L&-c"pTH>20fJhGL&lZC$i/TX5,;Fo4'ltuF?#X/hJ3.&)Z;C&D?l2nkE(fc"atX>;[,"c?``Ad6fIfeWeXPf>$JHA!HeY+lqCDPn)*,R;WM'O\>,fOZ2VboO7RY&DY5dX,8&`FSmK`Kjh_ohkkk8]%7I(t)Up#3eDdKs(T$[Ef>4;)U>)e;=)\#;f%[?Qcq$EJH^&2uA)&BWll^n&@_&@mmfku7$b]t\-Ba-#gB?em1GdKi=:F8#%VG0OOSKmX$J4mnS`go1?liVGLgnrcBOb"[iDtV_G!*04k!&tO7Xru"$QfBVU"?5/>ba(nl]O6>./pD.M8]r)dpoo7Y4RZVpMB2`b6P<[emeUA%`/frhq&]DC$^uXhNJC;E)D4uPTP2A9pH<dFeZQ2r$@/9[I<@.9%aMWWgDB`"YfhGI<B\t_S..6<n.o3ZK0mo)/W28;Uhhtaa*".Nj$d\QNmkJn.C3ei.bCDP'WH/QMc@Z4O!V54e]'qi6(%>MM[m2++AgPWTc1k9D`Or1J$DJB=g>U>%?GDFDa"7)8?m)c>0hmUn@>B=1/'r]/r8G8P7'q&M-`=i(CF*k/D<dU^=7<VbP#YtLSK,2GIK%q"<3i)98^6)hhcm+Ub4e'PSt?8]uP1??:dtN;iR%0d2r`DPsaNKRHM="c7P^F@,.oc9=(StX/MLmrge#F:`.HgM@mZj\CE%5Bq9?\"kp,%i]]%3mRt4[!^2#1^&c^8FVmI_r=(!;-[b~>endstream
|
||||
endobj
|
||||
62 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2894
|
||||
>>
|
||||
stream
|
||||
Gb!l!D,]1[')p1[kV&nZ.F?A0hY?49aP@TtP/&DOW1jKACm<D,=-<m!^](fW,[!%6$/*QEhBNal*bi3`+8%bFcX74ks)IotaT:7)_,$sK\:CUdE9/OY3eNWs^#/NGk!tP1i,QU3+9kV,2;Kd%>So7M$3C]dm!BrK"7WkAKFI>V"-YZj*fl\I"aDMo#'ci!fp:U0)]-jsLsJ`j!AC0%m1_pV@FS'aH[-Q'*.*9q!$]_$O@AqP%f7&TUT;f,0-AM1E-q_RUMD:^2U5*I^)EQUL8utHoqo,aecX5Gq]WQ+CnD#00]hXCQcV?K78q&ph,LtrQ9Lq<8I=!8K^c,q-L'.s8ZK,;c8LG%"!</&NoZD'l>*A0$,U2t]O8U#X^CB,d\8Hr;'LfUPI)bD%QD-j[X_`)?4cp+IFHs:+7=n/Ee!<GM+Do#*(Q6B4Hr1dW*PqA7QCb<"(l.(0GIh9_[&TN4eKb<-8aRjp?odPB/[.dT@ge`!YVDKa*9<L"+7@[lj$C<1QN,*l$"lR#U]q6G]A:`Z\nf_lX1s7"!AEd$Cr_;efZ2iqck_lmh#71rr99Z8;G(V&a:uIP2j1WL+W2qILegCg.ac/l/OXRbP'cH*k'S7(/NS0_a,Aj^Km:3qUik%90rA+."PMo;.8Tb<5V_2@;Ok$f;>FbZ)LH;?Y9&3?!iYE?_%U<^0@5'&74#P!G:4j$P\__:!8po3$=:sa4F$.Fg4S56(?B*%sO$3J/7=b^9I=HPAR`YJmLVJ?i$AO^"1i@%(?e@a$BL2+MP_l0,XI^e3M)?=\X%$riX9fQlLREl_Z.?@Y$:%f$Ul1,NODIV5\t$bU\V9XY,bX5Xq>aaTu-eOnFT-N6ueXfDCOVFOASC0WW>fRMfr+W@>j`!%'/O8k`ER#>gXZ3^J8:8)Z=gQ)Uro_\`6hBgVMscYr,=>NV\Ob`_l%1nS?6qXmrP#/bs`UeT[hFloDN1mj&dWD`X>a:O&Wo,>peQb'iK7;+IDX]ZC>020@`$X6+@8e)NseZG7:"Z6'61Uteq#*LO.@?.kJTTl\FTiBr!+XgEVD`K)]A>3Z=M7j63drX$udQQ=q&j3OekaLE8\n7KFqCicI/1!QI1[&,P(M;iq^`@fO"3'+IHs)NmrX:F?Oa7'5[o=b,eqN:2NHcaC>RqMp?78@WoJ'g%n[p`AJ?ua;&<C%e_8XEU5`orK,J7nmP'.b!G^Uo+F%,h>i1)tp?G7G,[3d/e3+O)j2P3c8QRBS8Er8cu,!g^6O8\ZG(LZ0bcI-^EhQR4:R.M#ZNUrb<(8U-GBnOTHVr>pr+_K]C/-G\nm4qe'WG'aO8\8-*B>4fW+DA*#h%$_QF>Nq\IWX',Me*)'?$S`TdPimARU#?mX-tVbQYG9C>XL^$Re1QrB-r8Q*9jLTLtIOK(>RWmX_Z_Q-GRMT%gMZ.escsUJPpksJK!3i`U.\;8HHF-/bcQNHI=a&:a"FMR$7::q(UE7L>AKjFAn64Z`X"1jg(ZV]YgkVVN%0j4e8CUM:NH8Hc3oWP'L);hGfkNo0sgFO!$-d1Z-m1;AVAIE%s^_A8,J\!>YLDMUP!o(<r@aN[f/XUp3$"PU;%d&m?fWY^ga/c&K93goEU0J0i^i,W48"lWO\8Y!R6aQYpb85h.8ZNtA>G8f*/E#)tK?::D.Cl<mXnJ.cl`2:[8CQ.'KQot[NWRVKg"o\oZ>2S`;)X+6bS^Gn]5WcZgV)#GStqDTi2/Bo,8QXa/\+/?iYOH>9_lM1iSCMkQHOQj7:&QY\h$HlZ&BUBWIQa'b/S5GE!K!+:1)X1J(>N;'^)ZH-W`b@.r2P(g2Prqd@1,HKkW7U#pb9fUI9oF,Tb\'ALD\f+>lkDPTkI-8B<i=?UN4N?+ORaC!AD><+^)$fiS_30&:n5/3+R],+d.UnClP$Sk'6<>bJ@aCUBu&1Pd4*$*Sh<j7+s2pc;I89k)2\4F$_rNr;F!Pk!5JCN[Lt(S726+cL%::_<bhlpLN]asTekHX:\HCG?JVIPks_jTS\n3D/[SpOMCfgY!=LR+hKg:3=kOZ7GY+RWOX(5.cnsuS(X`XccGuqt;Qd84]lc_@a^6mVak#$O5I1$o*k`$Z>DZagrY@Wm1&AKuW(a>YD\fl_@?K$r//m_j(;XC?Zh.>`n'HR3qR\m5<OcWQ,u;,4^6bP.`=LARa6CNAp'?.F!klcn$')FA?Qd9%mBP(5$?=9;-cACPH\D/tIECLc[TtO51u,91l9o8eOj'c\`A\Q'HUc^hZt!1Vro:n)HiWV*a+rUS]KQ8WokTrH\C6Z2_QkLYiAA9bbrniA.fHFOlX%S[U@VNE$6uB.>$sYcKO!D?3I7dm]A$4bA#aq#I@8=rb5nm5jX6<Zdu's>K[>7N/>d-6`H(K:=h)'NoUJfp9QGL\`"==b-V=uU]6R_WQtg_S-FuB+d88/3QMs&j'b]k6R8R(7*c9M0].6?8RZ.5R&P,<eVUHPb;PcJ-BM:V-RrjBn*_9'9cp?K:EI@WWPad:.15!:%W'(oLJ#Bdmis(I52fB1f`b2lcc/:61oTu%/P$i"[gs#D?b'?p(c_'#.TdL@,?.;%-/;^SUOL/0eXUM8BK15b5'Vsn/fb(f$dbguu2'!DQ5]Sfe$IH7CIB].$^\TCWA(>^04@>G6h[*-/0e3<iL!'N39Aui"&tQ$>$;`623W[ro>tCp>rlZ%LbR%,BilaT;1:8YuQfIiC!d(-j`2Wh)I',tXK)ikt>6QDcj)5O&s%DJH$jk$^E#A5Uko2K.+R(C7aJ^:^r_*%hY5BFEntVn9);\Aj:,[Kdjj+a&1$sU@RqhkpS13+uAP05GQl&NXZDpX&!Jf?uCItTHeh?dCYiD,=.n_dOnIdJE;nPLob9L<hk:>d=Y0p%W_DXb+":a_r(R!6]3W~>endstream
|
||||
endobj
|
||||
63 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1892
|
||||
>>
|
||||
stream
|
||||
Gb!SlgMRrh&:N/3$jm,VDCimUpJRXpC?$!WOuA?Np-jKtY:,h'?I[er'D1TP6gj5O9<]'dJ1XnJG=9_q%YqqX(;TGlr-)HKr%run?OIDX-?B`U.@@c%e&ggG&`YoHV#i!>cuB7`6F.M/3%rB59B0-)c8:`AN^4&mAJF/1$hJ!U-;kmr&t$h?""[:e5;HLQo4!"l3'O&QYa";B92'#Ql$@n/'tl8'`h\\NUk5q+V`:o6PP9"9PO[tr5J.rQX&Cu":SUHikM>B@PO[D*0_l!<B>#A7*]$Oe'/Xu:5F5&a(%f8@o2TM*Y(kE6CJ6[HZrjiNL8gCDr*3<E2mZNP9QAP6@991*U[u*hr+VXD)QBY>a`pW8\^[>I7pCVH3nmUkJqtps?+jh=n0[]8r)(Jn<-*2[h-1Cr:IjK[CAVPA=H[5EKqCm[)0rE<T?;Mkjj4l7jtJNP/<5l-@Z`DtZ317JIsl?O(TrA8>S\(kQ?SE,DM`.)EDd*88SecNA#:Lii1Mp<`H79]^7.IW>7m\kDUX@/IUM&PY;M(cq!/esUF3L=h6>-%E]6Mpma%>,T/<fo-G=rd4lBjWiTDM0!ml/BLa""jTQCQ]gj?n_Ik$X"(E%+(eF0$h8ZEskFE?Z>(r;1F8_>^(^OmH6Q\-d>lcDI7+r_o5>\!;/`DPZ1Qs%3Q2bUdJ9FIoP<3mL/9g*]SD.,Ga&p[X<q.k2+j_P`l9KuK-5$.pfs"L::dT'AgkFORWJ[RAjWeKn:LEC1%G@4'GRa0lr(@o%i`aEtoY!.S9+s/9ukGtFrY$IYJo)dCGY$>iG5b5#Ne\U,QO([4.X;cBobJIZHlqFaih2aY4PihSV+L(QQ<2o5XU@u=#(QBjQ72B?bGThN-O!2NeV60*STZeFrg<qfiq[eK"Zar.,&)Nfe'V<e"92kL,jdk;I<=@ogkaYF6D<pK0\Ge2Y+N)-YUFt]0U,W?OoMCZqB05\e+RA'84!HB9>hA?Nj<r9_Z$HFoDNU;_Qo1,LBH.6%/^XEQT8t5VLRq*42%"/b&:!#[pgqAfp_<-bU$4VY99FN(S9l\G*e`/`AB9+L$p0)8`Ou[AK++/<OC;d`nQs/IjQmrRe@<g$NNSB$77tKc;^-k:PX;O8Ua[l]%iqAm4Y[i%3gE<>XAcNpWDpk?-ZU$c&>@L@mLI;-.eZU2o>1jC$kqCKE%$Fa=O7/t.GkRJp(rsScM+*+lbKN1+kQ)J2Kl!-otR^mji@1g$=h\OKM&p)C3)po>7=-f@;AcfP^aX$8<J?@L-B'(LVZ-QLJG;@-D#PS&fYjHMDdY-Il#r^a<>4Zjp6lSas!.R0`MD.%)VU43CJ<60<r2DNE,W*/iDM-R8M%&E_m@1MO0iQ!L$;unp5\4$>RX?S7FRaZ.<8kj5^[#?A2TkWfpEKJ]2X`2ha8ZoclTo63KZ7cI\-k65i$q<T"(D;;mLl'`!T&Dn1&;p8f8_.56]I8Km^BPS=Xp7DB_h'nirsDE>D?Tr<WNfZ$c[@29^6k/(GOIa.4l/FG1Rc5*`%2ttJ&I78aOMRm!$DDcL#3I8k5>ZO!?Cd[qXh:o)$QS2'Kn`"FRFYRYC$Z1<?6'1r;KSU2)AgXG0_`giT9ZJ3Bo.F8@`InJs2,oWGk8I9NX4gQ`Wn^q-!/dLVa[Jik7ZK/t:HkL2!EbMlq3<PVo]bB]f&QLC^g'cR"^[0q\TQ.ncOOML6[61fh38^M9iZX--QHIq6&$MW[RWop\''j%5<_2.=D8HM[*gkC5E!L\V49BG.Jfnh(fH8,VD/LdcB\+\]!?:A\@OcpIM=&3-lj`$Rd1;+Xe%?7iAp;I?bWCi>GO3^`-?OjkH:6A>(?O2q";>D#L@q"Q.r=X@!!Kgr$tF"/]-<_>gH[0]H'/h@2SqJ6lE,b~>endstream
|
||||
endobj
|
||||
64 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2897
|
||||
>>
|
||||
stream
|
||||
Gb"/)968iI'#+6EoMM$>)$533c#K8Uh+Di2>FCq4W`62.a[erh!D,@7^O@+%T=-#gNlU"U)Q+3'+OKJ!J))p^(]P'<mkP,m2hSR]#S7?L'bK9m@J1;YAk1a>*&i[`l@VGP$,Qfb$>Ougi417USN%#a"A.Ou-jUt=N_@%sGAp[_Eo`>7ob%UlTBI55,!U)A*%1-a;#`qnhpXgJ`;30F@\c#C3sK\i1pSB`6O=2F-E\#m0lG3DErPT>G4OTV(S=I\r>Ak3bWV$Hc)7mtcV?[<J_=+lrR0'?e3bV1oHSbFDYbu.W9[[an)^k#Gu7-m.kAT=\JL@8=A"rZ)SrC(<[-YE;R#NebBAg'K`MNFQK1!-b.6\^&uahoo$jLe^&(]"&4OQ<+]4*;M5]LnKtE?.p?MAb-]A/Z5KJ0,k3b3Dm\e9!A)E-+,hDBR,*.%>C+H3IEZi_AoZ[#:7%M\_@Yd/+"=/iNU%,*tT(XfaY&S=43B$+g^dH\t)(-S)Lk^&8+H/\K4@-)p5q=aJO\%OPM\YmB=+oKj0A*8QJKe?l>'*In2?[;[E'f/=r)Nq"_S9H=:t$!c!n;<+q]8&;6Po4LiZCZ=g25#\Y1^j2#+(>:c&+SdE;L[<g$mAOm_,`lRe3j@98nN2XABVE<_J-)eP_qf=ie![3J1;JVr,d.54RPE06oR-F&1`+$kPA%0c<jR7%N_mj)0isFFl6MR(+CJ[:4N8-J"&gjn7!fN$Gs*Lg0(u3ZreGeBe<<CHUD(lLsY&lig/2RPM.9Kc%'LJccY($-[;-.R:P&CPJ;10ZB^1Tb9A@V9EA%]..-_^u:/&^Zi@nlWpJ@Vm'#b.e*`odOsll:#$'%L#]t]#IBZSCKA=HR_tRET#2#\UdB!3R=1dYKQ8]0KY"6>gCU1H^^9.hQ!ueXe5`V-T?'EO=mi?I"8mp&.mA9a%9s4,'<bM]NVO,!UHZ.a19E&eKeL39-ou"7kX[!T\]rgej*+4K<MhXmp'oLjg56a!6sI2=F6K=3NKJRrC>G'b>6Bk3I[YV:?K)/6cA/u%HX>ZOmY+so;RVeR2Dt<84(N&<2C;`5Ic/'tm?6`%]pNI7SW66FDi*%sI>e^/]$<1RI6C=ZX&0lH3m3Y*HG^DICCf@rOU3YW[j>(bNf!/W[2Fr@jdpOI?4k7XmSmR24GiWa-<PG<;t;c^mqh4TV!HF+d64h@\_E5hnK2cm(OTT=ZaTbDRfTE[`DMh`$ko<J`Q16s3/,I#Ad<o_+4?IQj5KTHL'j,sWi5XH:m[36cU7:Q<h`H"1laP`nI1U"VNsoY759KB9,V=k3GgdKPW[uSf]#Z7>fsXV(=dmn]\cbo)mhoGnVWU!Y:r7[fS4$cI4:!?b&/_K>B!';qsb>1pYB\lT@DCK>h0jkH.?IkY!g9&==V/`YaQ[a/B,3!2cIddRV)J`+U;_JZ6pToE`"ai;N%Ve`h@lG<7kO?n!tRes,8uB?j/IQGG'ceNY=M`+^4clOUf*IQ-+oBeZu.+'W##jJn'p"V5KL[0euq=Y-?R/B\9/sZ8e)u3WYnn=FOg'/u;IYg7>#`Pl/LQe'l.t*upgFr:=K/o0"V-Y9[%\+%POk>bRM$^d!1M0f:j/"Y1p?<`Io3q.,oXZ;HE(HX"WdDS_1Id!SMt5,70TkGK^8B,+c)1P*b&X;o]?-gL^bEh6WHCH$s*O-cfA9V>%EI:/f'RZ1d+fnj#tn7GrSRCKkol<B)W29;^5MDp?P0S2'44GR4t,d4OH**_B0<F_4DM]mVahra8po8_G23QPEB0U0:e)k4W[(5$V5UR1=>h^9QMCC&kSp<A4rWS:pTN_cArpMmU)BX_VBFK[fXmF<-1B@f[j(\P1ConZo(f62:L?fafGI^&5:oXVdrD.b6-6qKAI%E!f8S&Wia(u%gra4Rn:0K/U5@r,D"*6&KWK(?riCt.O<KX%.7q(Rd+"MtCPlmL/.(h44<r-nt)0mPpDD34?-9k.cm*KV29qit:8C0\MP;/TnfY(U\X#FKQrZ,@DMNr[<ka`CK:Z#/nbFK,.S`lgX7du,mJLQHdCo=Zmn?e1#PM^;`/)DV%ecWir.\P%h;kWs7"fC9OC&S8]"=pL@=<7q1*hqGPZK0b-dV9dAY$KXaiN(JfiZ1ji'o3]5,I3`M"*YW@"AKU,NXiBh>>1:<VH!/UjT`;+3nsU$ck1Ff]&o0n2#B"4*]\p@/7(E';`%%(tj0fmJ:$6sD-pMo\D-3X2e<<RQ[h+?8[FBVFZ!b.m?EnDc>5W`lbA,FA]:i/>hC=*!C4\#GG^2O-bX\0$2<O")g%2WoD:37BgRe>?&#G,kn$LEU3o]qbTS"7VO,&2p:no!>!6)PcJ;,YI%<oYA*`+*<C'`bRK@mZf1W$lNC(I8PEj_*E[qA*NY<2nPh<c-^l>C_IW12>.J=8sTIR$X+jLG<!f1`A.V@NhC:;nuP^u6Lg[#Zp6+LXaIoQp,pDB/VQ@dCelf72,M?P-JF7)9Ea$H8l]/]p\8>#P4k#R6"+<WcXQVGL<pW_3^2f&L4o^tM["/6`rJQ9HgrIR_`bH0=c@S<Q^.M'hEFa8?Ct3$m.3R\?p5#\b%`'"BIQ@b%6D8\WR*'!dnA/\el_o%SUur<dU]Rta#B99#N%)6`,)G:]m3UVG3657l"hH2.)#0VUG&6.e::^BBC"W@#sc#RIi*l[`i*0]DO)YmF8&^h@D_^GO.^Y8$aZLt'E]FTD2]hO0<%W"_UCk?1uWn)d5lg0j1'$Wk?*<"s,=%.n`aaMh&,_-s@ZeRoh^59oI.i&@Cs7Kb$h?)@3l1C,)4C+:]mI\Ei%)l]hqXp:L`pS"J_on(IHq`VJ@6m^@aYeP,Q?ED%na%^W7(!AH\bO*,os!7Y$JCp1*IWdXK(b#DcR#UQgnkYj:_pSJa$ZVVP~>endstream
|
||||
endobj
|
||||
65 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 3241
|
||||
>>
|
||||
stream
|
||||
Gb"/j>BAQ-&qJm</+Ct)2\Vqh_!=?0Pf.Ee-I0S2POm]M%n&`>;Mn9Wd^f;PmK+Y&ad/Ed->ntkB+csFYSV88D\o&a$/c>8s5ls@*_jaP^G*MaREc*HBh5*samo#]cN>(RC!&e0).ug:&^!\Y32iKWOp*\%@n*1DU-+9A&ht;D&OQppjpqqM9*:;r6Gj4-#Yi/e\>).Oqo/>o5^S&HKVmD"1GM0'e%n;ZauWn8N,lpJP-TTb5me'fjRC.;a`R*so<ZmPH#cU(-Po*g3&s-N2Ouu/O\FYol%;_@8rm\>ne4j1>CO@:%mhrLM94MKWZ"6bD,\X?6:>X`F$hQ>:WR$Ec#o_[>g)]8ZJD[>3!/8%4aEl=HQb^a#cE4icN>6aZeql.\nY2M)&PcG6oS_RE;)^.f#'-r)Q-FDIalgc)@tY4jSmQej(V&J/5%_q=<b7o?@+nHkN0dYQOoR5a+VoD,Ul:fUCkZ^#\^^e\@ZOq/9%<-LVRN>.j2WLf8!rhM;c;!/;hmn/C._R7+D+X;D=7R_8/50d4/T<Fp`DQl"D1@_U3LjQo]d!$9VkhLAu]*rW@Z-*5G)m4GmOBP;Es^m#D0-,A4b4J89(Mj-OFC*(t>/4aVB!fXMKo>I]%?:+ljcI@.V1A*5DJiDem/iXiN]'gs[#Ff'"n/5bJ-2gQVj^lo.^IV!FW]<.S,OaeGdc=o$rGDN]:DI@F(EFG4</;nLE63`Je#Gq/p5fgJY%L2ZFL/lI8On^#1C8!0+aB>Jb%.]/6V-\_kfLZK=0.[MF.ThW3(e4![iFNVVS]2q!a4)ua'B*.*?..A^8[2icLU-B@U6<7('9Nrm=HYs=G6j^%3Op?;+->dG#?;KtCh>Y_I;GZV0]iY$T,??7`-\-McG=&_d;]inEK`Ck_M?CbF0_#Va`2o<['_G1\X"=J<>\WT![F^[^l_)dZ>+7]dh'@Cg(`rVKi$/k>9U^Jias/82!WuEbi=;c=GGKV#T:YT>B9M/e/6kHUp_PP!=MbW=9/6"[Z+H_8u;forj,VtHW$eX(j0*c6>Ee]F_CKG/ta*+kHaM]#Z,mS_42c)1SUo)/4jV]G]d,j5st)uH%[Q))\\ViCI,<L)n=6PR8?/hQ>f;\Q:*D\.,;6]0W2IgcsN9bP*N*pO_k,LWAQ[*fG#0%f35j6Alcf2$k/'u>,%=Y5o9'"X\>_cpD\Dg.s!<,hnJ7tL=#kJ!/h-GrLBHFBXqgWk1jo8%fD50']Xfeohq/L6\n.][:_l(NA(ol==3>oRP?;ICbdT`iQ6*dF3"_9jVl*.itaS*0cDa.\Hs69`*q3u9R(C?[Q3W3jq1]gi2sSU/%=6VBc(J[BCq,9bU>Kj/>gc=ZcA&Cib-:CO7<=N=HXrH@o"%6,e,ZTP.MF0)u7,+Nk:%CS(2G>)>G^D.Hg,Y42'EmD`:4WH_8m#K(fZ$-()nDb!Y-d9[f*>9HR-J_3L)hpYXWLrcq9AUe4&8-k/<`'$B!Th^qO8^g:u/E5'oS*<p(3L;^B!e^Iu%Y>.lt<mu3TQ#uS7fAYJ-RY%f1S93hE"7nR$A"]8hZSa^KrjGkgD!&G!PT[S!27Gr];`=:6p"_ZD3tdO?#=BhW>G_sgIAZMaAI;@$dR)Ia<rcR0FD]*(Q<0*(<gQpX)(VtF;tlM&Lb.n6KVgP#[Z.iACF'pBkm`>unIcmnWJerFD3d>7!VDNps7c'RMs8@TBB8`(a3V3Hbuba'ihsO3T:HWh&'cLLkJuZVjQtRAZX8."=LD-]afI=\J7p&dOUHF+!;'2g+kn/C.*]VH8>f<)inT::@JJ.&Q(1FH-iqna$HE^a(TAob@N'hVqdc;BFBPX'B,GopS4/Of1p)H;P,8-X8]bRV!%f=+.953Z97h,$YY@qsce:QK\3\khX7%K/fEL]hSg<YYJUJm775KKcgmnb);>):k!I1BQ'61UK#pd`OJHKKcE#:?]"E!JZ3t\):'0rC]#pel,a956<nVat_@`!7e5h&CYUgLjDm0r8r]p+6KS9]X@G2rZ\9pg`(LV#?R<IKkn<2CaG&(Ke_n@=dKlmcndD*1.,@\pI1!Wgm+d*HMP4/G6aiQ(2NQgHjcrtTg[Alm"Mn^-GcRj3K7L'4=%CMf;00GIJ82`\R7n$*C6-V^A:KdCYV'nWB.\A[@tN4I'H[Ru\qQN\-cQQuGN#Bq6f5JmBu<\5Ma,fp8F2qKTMp]"6phm(JrN=eD#[YUOY)oPO>T>gDN<ZdIV:%]NaLMf8eHGNr=nK#I(&<DYYF;bgBdtdskphO!O>K$"'h)]:4L`]+6S]&RNKK\k?1?(N<X5-fV(>%HIQ,`hT8RC^_@t:FtI6XfglWj(9oAbsV>U$B%b-]`Ebcbb[2>pE:P1fsgEGH@2a>JMC\L"dkY-p#[6P5,l;8&+cddI?4gfRMDRTa%C[42Oq..+Mqml$Cre(-^:p%neXM8+oG=0aCsDm35eXVtl%VZ(D>eUY<T#sG<<_W2V@\Q7g?JH";-k"d/#!b>rQ/"HHAiS?Bs>QA@Z&Gm_!#p].`2@FbdK$=Z%Ojrhh2NM)?]5.=+`b!Kl4m0R:k=']@AM8X`AcL;#ris<4kBFM!5g'3YBY]fqlF+3ro_e[KIS+nu^cW?UC\p$)Jc_Or(AH/::n<e>YC$fIm<]-&[01=Lpo@Y(A84i?7[NJDg[duLLWh+Zh=hBN%h4TL!EiR(h@!?`Zo$?^9e^L*MkcH3)]k(H#maeM*^1rmWq78\o4"c5LS6QTbfIAGinfS`76`_4Wg506nb;_R^4+tkmsD3e"'cB)Y2B&Aft6-_rL\%W;OI-/GDY(tDM9&IMYp='<4_f*G"*\gqBbc($B1@Qr(7`R%A!_>SB!`/fgGh^b'di&8h>]1^T\3Uq\l>.pdZ:-jbPD&HkF-C<SLmnrW4>^ZD_?`@VNMPQ)]g$?$3k0btOFi;"EpWFW1Kl;d0:Ga4hRm5.gEc!7B>Z>cm_-q00*U^Ae=SO7DZ[j8<gRhV!A<rThAr0pJIaTOeS?.=PobN*=bDWM"RV&ebUZq8(i"a]Xo9`]%[T^MDX*PF$f&!;KU>9_@h&)_Rdj*(#L1%21S%[':-]"^bH6*c>@$^V.jS7.aj6X86#sK)=Ot>V4"R!mifU.tR^oJ)e-DfkC:)TP7h^+9=HMoqFhQdM-O)(2Ye>qfRJhY0-r?4]?5!5XG)D@uk=ldE9mclhJioK[^>M8Zf-q9lf$\0D*-Jdt)GMT"ha&?r-Y_mCt(:fuBmN3VX!H+eHI~>endstream
|
||||
endobj
|
||||
66 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2357
|
||||
>>
|
||||
stream
|
||||
Gb"/hgN)%.&r,lR'RcsKZFL+agoA'WbW&h[9%H"9G&LWNJga/Z9EGVf3j\GMqZ@N]fs8U9VoFqMY#DS>*WU<Vf7,^j7Nqabs1<$%I[JSk?8,h#bu_iH+JJT]\sT+;3XN[1.&)@r(9o\H.>Z'H:DD[/b:k?MN)<cU6'S8bV(;nI35ZD]<o9MPX9diAU*(o@(CK`/%"I;[2SPNgT,c1tmY?D2]sCjNmT*F`$cH&RZ)'"(Ld`0r/.D*R^k3*@hRknAT>.m`p\LD/\tJf\=)f5=bNk(9cHfjG8+Vcus*cba)a+R,2h$c9&!%f%5nkZ`pcn#CDhRC4?.dHlmOCub>#q)pU*uo][9C\K:mL,7ra^dYM6do9hmMNP_UtnKM)@^f3"-m_;*'+%fkQ\M'1Q[q&M'!7hO3kP&AtXIj"e]51e`pehXp=03^!.*iJWR=aNLl.pourA@W1pIBC;nW1$C41*+-pb+H,iCnsBS0.rt]M]:m!OSX>*Pm:ZQj9dpW`l2I6HA9ipF#%a5<)-1(\CN2DhbR,nacL<P*HLqmh-(>2NqK0ai+[s[u2e(_N%,HFiG1Z`0"960oO!jF=*i1;JSJOlAYjO'\2gY5m':AK=>^YRtO3a9ODdr1=KQf&%gF)p4h3Ztno8]2aq)G;cGDo-!UEWP#X9,rXWnjc8ZkRVre=\oG8/LnCBWe/WYW_UH08G8GOJ-q6"o1Ofq"sD`jlFF6=ba$%S=:.GJ3,]Lf#N<f@i\;K5;7OemZ.</8:)$Q+/t'*:_k'AfoD-"!M#.1pAG)@a8(.n.#:5A!)ZAnDXN92*^ds":`"N#0'Pg.AF5$BWL$X\nN&j#=]%j6I/&9RM(Xhg>T_!b7f9]\,cDr588%_qSRR1Dm.dZO6<J,\S'?)VBrO%2*%P#:Xrnt4r@!]$,qru#3q>iFKJk$H35paKk%At$:22[$<u1[%]U"i1[GXMG[AGf\TL[BR%R#o,9#b)_GMLW@[hVtH/T6VF94;Ln!p=h:kl&IHM2&%tl5c1K>-FV?W.O\"\%ub)lj^m$.#0<#'ZucbFrV/SGkRD%2e)S\@AnpHZGONsBUXdX)B+kjg^"BfI[Kj(&_b^3$He\.%;cE'&f(br5fB:FqbVT.D>bAlIkB`0gb4+jL%/`.+',R0LANne#C@-QNji1MZ?*B%@5L$UWu8)jh5fs2pRbL5he6SS\G@hN9=<.%/7$,CqNMntqG(8MfOi>q]@bh.egF&<)Vmr"?"(DMcOo"(=rZJB*m]2VfA&IhnFSFq:VU#Wo0GP@\C4Bp50B(R4X]/6W66Qmd7!c`q)&-^Ya5k+qX(e>O0:_B@'c%'Rr@e*Lr?7D#ask:O0/P9)a)jn;F^S.^#Y5-:/V5=c,?%Bgf-<n^;*^-B"'_H'.7S^cqWg[_;gdY_,3Q<X&M^B>h6[A[tbhg,43h\A;.m`4BMq-s/[2l[ahARTI02>m=hkoQfFAK2YHV#2_+IOD_#BQ)OBFMBPV"mG'20?#a3p''03)K!=!'<nA;5uh3OKe\2u_lLN9CC\'!DNOH9ruc48hShg$YGH"]ZIl\IJX'2><C.ThC*ZuE>$!Xq;Gf$sDZ!6fBQ#LN0s>oXq;0,3BnlQD26O=?I-LlD!k:ScXNjO#2D9s"K^8D*S)u0u9CeI_-i`Yds#C#*X)MWoi.%_2KDOq_Mp\\/p*)'VY2HM<mUL;GEXn0kKY:N=`&1WJ@!mM0BBAoiUqS5B/ajqt"c@aZ>r1IHT1k_3H7<UUcAekL47D!0M7^.N4$3]>8$tMj+*iD80-:\jS`fnn!/8JedR(CiO$`H>56RG7FSr[8&j?r"\-R;kIi%Km"FJ`nLD+1td6j%ifbM\cj#M>9H;pn)@@.g0mWe5lW!o2*W8Xc>[Hl_tXi$Re(A/Ai;r?l6@CNO_2$Fq*>X1sTmq^e5o<q*$H,2#9d/)mHb+d^&a\lYP/$KoLnps3^GAL0)j,UfX#pUp.O:$uOY^UCQ^#Oj-eFaR/+JH:*[4C7-d?Vb^a/0/;,koo)-,ai?X..5;f3/J^TKVM(47usUG=<b7!k6pi>/0%&\i5o/fF9Q!`r5EnY1'Suqru!to32,#WmO61UVf&2GH]jJ'jl"2LQ7umrLr/f>K8US@cMXDM8Bk=MTTiYeV+b#(>dK^'f<gH;*BK.T(`2^."e\mF8I'7>Oc(Z1I5bsDQ_cO(om9((lB^5\CmG_fG5!:-+MQnA"9!!hmcU7[W!Vt&",X%-%\Y=1AdrC7lt/OdSU:2o8dRoLEhp5J"'@LCh*9[L3Vm2-"a8@IpCLe]T;UF@5\!1dWo`WP5=R!EI3a9eCWEi4$+<rSUQH._<!=hZb,JmI('9$o`l]i8)R\[U6GTYkncp9~>endstream
|
||||
endobj
|
||||
67 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2281
|
||||
>>
|
||||
stream
|
||||
Gb!$G?#SK;&r,lR/*>9XYh<<21NuUR*q[6^S650DJRCpLj:+Y_8r?6&?f-'n+/3R%N_`lp2IobIIa-0En'"Uk^oqj0o8;c)Jq!\fP4WIn+rP\M/[";XEUr:L-_`Hj6h7$+UGsLEVe1?TUL0/j09'I5;&4G+'#bPV:\[57(7J8gSTln)60=@IApcmN&n^,bh<!9Bk+;eD"9'GsjZ^Qp'4,*./J*-f?qZf2^@Yo-V=5+[U1$Kgf2icHMdP=jI!7kp0BqWY2@+\8]Y")YEH1Xn>(!3d:W#.SHL+'_IY'#0O"Yn,(L]Q->FTi%Y.']LgMN^o[m;:k-F\*_q['s@ou[u[cAa:q:3PEW+HBk-Td>3PVS*GU^ln/CIX#5p;1J7cCWY(1h:*40Lm=@0SP=@aM.46u60?.#,f1YohUHf8Nh-&OmdnoS9<p*[r+$H8J[;qs1NcaD%/KrZa,Z#F`BfYb&kF[==TY8d<4Jk'M5>0)q\.V&3W]>JmeV0r9O^!4bG$tUZ18=8(.=qB1?Fdkj'Q`g%o3d93)":5:M1UiaCitI`7,5EGed7G*n]B=_#Np"I"Inar$]EW-YXDgX?.3-U6#=;JSAK2`E0^"r%I;Hn@cFY%W7)N$hnJol,,@HLoHT!UFL7k<7DlKK3G\jjK_d8QiZJ=e!7&eQED2"bs!iSrmXh6rFnrQhb,<"K=*@M>?G&KKUIp)bZb]P31%*bZnqc%%Uh(0pW3,L.d#dgDERKAJaJOX3dBjSiC0LTZX-S+1K_NU,/?M\?g.-f_QKik[V)CL;lZs'fqq["Mk-_G2rH6ropsB&Td[C875IlG)T=):gNF$p.VdKcglMDD*STh<E)S;,eXBAT1j):p6W?^2L72q<5+BQp9\UIi/(9[81`CS=1P1oE<I(K]RG1UkD5/T/PXA\r?$VL(G^nT\r:6B,Qp,Xn*6pane@G4$lmU/,'DJD?=StOj_?oJqMa8U:`9/,4B;tuMa`.gTc4+_*0G;DRljrT*S:X+uc@mWt1ifaj+/RC+V%"NC=*+P-2@9Rh6A//Sh/*m$?s)4`ZdV2*F_P^#eBOFAMN(mkh;kT?Znf%hF7+!<R)A1h?QQ?#oj(YDnLCO"@2i]B<8Hgs>+&!b0l/Z)ec:&9<iUq.I<\?j@K_^R\9W^KL^ucis*mGZaT%jS\RYn(@YF@6#D&Bs5GW7#P2reWHlnB[RCNn2-_&_/Z(R/(V&O6Rc\Vl)VK00'8'e+e1L<Xkd]m1C>C\[A8'Zk<pJ`SO)_+OVOnHWn3u2V"#\san]QTOEL/d2R:,c8s$W'JeN,/sTf7gmc6lXlSm9+fD[:WFem7).L"[0e'N-UF:h]RGXin4"h[;,BaeYQc$D0#<LT:j-()j\ZYM'T4bFjUg0f5OrFB<B/BVL;.qL/<6X`,_dcL/86`.Ye*Yk+S&Yh%-qmPQ'N,^\af/\sLLtA^TUd%rfh+20Gf.R6L&Vf=q^R)r5>Tb)FN",Ihb045k6d:.dE)p[hWH\SdAk3H1uthLUPO(ABe2[Fl]2GP&:+C9J--2H;)BCoLE5RsI5?e?0NFqSZ0:-suDY$\Rcs$[Pb2'M!P,H%U#rBZ!mN]0'B7q<caE8M2i;/9J;>r'Rm$a8LtNDN/b^I@:"mFh2=(Vs>4_\;=C.%dFV-2l^PG2T;8r`jB4r$'r@Ub9V260%;6oLo<=<:Oe(GITdUp(3<=@9KQoC7c7OeCqBb"[X=M$Z*:(`;s;2p7k]=?`YDA_4^ZgTDH&cp<**AKqmh;&r);[MXEoQ4CV\c.'gh$d1:U3_RWk'+iO#.YFCi7\AF8II>&cEM^d[^kc0mL-kVIN.M/2aD1P7.SH)7Om*AEr@1-OB\8q41TmW6/BKu/pG*(8Jp78Z1q&;qnbR[VGLMT$2'H>s5h=s;SP>.W\"U83"EDS(Z%ZIAhUHM0<g*/,?<m*(^jX5Q3A_$Tt&6LD+\>XS.LAp=<TaZI4)DH,FF0ra#&h(6I=MUO.NS#]\X`JG^MqJIZe-98.l;>MP#.)R75oDH'^_,uj_=i1A^r+HNq^>fR]]W.PoF9QA<'Vs]/([)7H]=0uh?^#^hfA?!ddX1"<F>DMcV!1^;#"([+Kh2'l)[N-9aL$@m\MH;ml#6;L3-M0AO:`2NW=0"lauOlT+i!$</2'*1!"*iQ8]kn&"Mg92G'm`WHF3uhBnQre1VPIQN[Me.,DTdSV/dYr;%l=hG^]eBS`QiJJ;o>:R"F6BWa?REq,$E6\F]Mi&#hbl)JXRS#DZ=)FgsE(3dO?ie>.S=d'&4^:At<,Z-tl~>endstream
|
||||
endobj
|
||||
xref
|
||||
0 68
|
||||
0000000000 65535 f
|
||||
0000000061 00000 n
|
||||
0000000159 00000 n
|
||||
0000000266 00000 n
|
||||
0000000378 00000 n
|
||||
0000001557 00000 n
|
||||
0000001762 00000 n
|
||||
0000001877 00000 n
|
||||
0000002082 00000 n
|
||||
0000002287 00000 n
|
||||
0000002492 00000 n
|
||||
0000002612 00000 n
|
||||
0000002818 00000 n
|
||||
0000003024 00000 n
|
||||
0000003102 00000 n
|
||||
0000003308 00000 n
|
||||
0000003414 00000 n
|
||||
0000003620 00000 n
|
||||
0000003826 00000 n
|
||||
0000004032 00000 n
|
||||
0000004238 00000 n
|
||||
0000004444 00000 n
|
||||
0000004650 00000 n
|
||||
0000004761 00000 n
|
||||
0000004967 00000 n
|
||||
0000005173 00000 n
|
||||
0000005379 00000 n
|
||||
0000005585 00000 n
|
||||
0000005791 00000 n
|
||||
0000005997 00000 n
|
||||
0000006203 00000 n
|
||||
0000006409 00000 n
|
||||
0000006615 00000 n
|
||||
0000006821 00000 n
|
||||
0000007027 00000 n
|
||||
0000007233 00000 n
|
||||
0000007439 00000 n
|
||||
0000007645 00000 n
|
||||
0000007715 00000 n
|
||||
0000008080 00000 n
|
||||
0000008332 00000 n
|
||||
0000010298 00000 n
|
||||
0000011936 00000 n
|
||||
0000014918 00000 n
|
||||
0000017772 00000 n
|
||||
0000018667 00000 n
|
||||
0000021716 00000 n
|
||||
0000023148 00000 n
|
||||
0000025847 00000 n
|
||||
0000028961 00000 n
|
||||
0000029856 00000 n
|
||||
0000032578 00000 n
|
||||
0000034217 00000 n
|
||||
0000036541 00000 n
|
||||
0000039058 00000 n
|
||||
0000042161 00000 n
|
||||
0000044548 00000 n
|
||||
0000047384 00000 n
|
||||
0000050531 00000 n
|
||||
0000051418 00000 n
|
||||
0000054289 00000 n
|
||||
0000057442 00000 n
|
||||
0000059154 00000 n
|
||||
0000062140 00000 n
|
||||
0000064124 00000 n
|
||||
0000067113 00000 n
|
||||
0000070446 00000 n
|
||||
0000072895 00000 n
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<1067f889993850e52c02a67053a8ee0c><1067f889993850e52c02a67053a8ee0c>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 38 0 R
|
||||
/Root 37 0 R
|
||||
/Size 68
|
||||
>>
|
||||
startxref
|
||||
75268
|
||||
%%EOF
|
||||
231
docs/planos/dev-documentacao-planos-seeder-complemento.html
Normal file
231
docs/planos/dev-documentacao-planos-seeder-complemento.html
Normal file
@@ -0,0 +1,231 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>Agência PSI — Billing (Arquitetura Oficial v1.1)</title>
|
||||
|
||||
<style>
|
||||
:root{
|
||||
--bg0:#f6f8fc;
|
||||
--bg1:#eef2f8;
|
||||
--panel:rgba(255,255,255,.85);
|
||||
--border:rgba(15,23,42,.10);
|
||||
--text:rgba(15,23,42,.92);
|
||||
--muted:rgba(15,23,42,.70);
|
||||
--accent:#2563eb;
|
||||
--ok:#047857;
|
||||
--warn:#b45309;
|
||||
--danger:#b91c1c;
|
||||
--radius:18px;
|
||||
--shadow:0 18px 60px rgba(2,6,23,.10);
|
||||
--mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
--sans: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
body{
|
||||
margin:0;
|
||||
font-family:var(--sans);
|
||||
background:linear-gradient(180deg,var(--bg0),var(--bg1));
|
||||
color:var(--text);
|
||||
}
|
||||
.layout{
|
||||
max-width:1100px;
|
||||
margin:0 auto;
|
||||
padding:40px 20px 80px;
|
||||
}
|
||||
header{
|
||||
border:1px solid var(--border);
|
||||
background:var(--panel);
|
||||
border-radius:var(--radius);
|
||||
padding:28px;
|
||||
box-shadow:var(--shadow);
|
||||
}
|
||||
h1{margin:0 0 12px;font-size:30px}
|
||||
h2{margin-top:40px;font-size:20px}
|
||||
p{color:var(--muted);line-height:1.6}
|
||||
.section{
|
||||
margin-top:30px;
|
||||
border:1px solid var(--border);
|
||||
background:var(--panel);
|
||||
padding:24px;
|
||||
border-radius:var(--radius);
|
||||
box-shadow:var(--shadow);
|
||||
}
|
||||
.rule{
|
||||
border-left:4px solid var(--accent);
|
||||
background:rgba(37,99,235,.08);
|
||||
padding:14px;
|
||||
border-radius:12px;
|
||||
margin:18px 0;
|
||||
}
|
||||
.ok{
|
||||
border-left:4px solid var(--ok);
|
||||
background:rgba(4,120,87,.08);
|
||||
padding:14px;
|
||||
border-radius:12px;
|
||||
margin:18px 0;
|
||||
}
|
||||
.warn{
|
||||
border-left:4px solid var(--warn);
|
||||
background:rgba(180,83,9,.10);
|
||||
padding:14px;
|
||||
border-radius:12px;
|
||||
margin:18px 0;
|
||||
}
|
||||
.danger{
|
||||
border-left:4px solid var(--danger);
|
||||
background:rgba(185,28,28,.08);
|
||||
padding:14px;
|
||||
border-radius:12px;
|
||||
margin:18px 0;
|
||||
}
|
||||
code,pre{font-family:var(--mono);font-size:13px}
|
||||
pre{
|
||||
background:rgba(2,6,23,.05);
|
||||
padding:14px;
|
||||
border-radius:12px;
|
||||
overflow:auto;
|
||||
}
|
||||
table{
|
||||
width:100%;
|
||||
border-collapse:collapse;
|
||||
margin-top:16px;
|
||||
}
|
||||
th,td{
|
||||
border:1px solid var(--border);
|
||||
padding:10px;
|
||||
font-size:13px;
|
||||
}
|
||||
th{background:rgba(15,23,42,.04)}
|
||||
footer{
|
||||
text-align:center;
|
||||
margin-top:50px;
|
||||
font-size:12px;
|
||||
color:var(--muted);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="layout">
|
||||
|
||||
<header>
|
||||
<h1>Billing — Arquitetura Oficial v1.1</h1>
|
||||
<p>Versão 1.1 inclui procedimento formal de migração controlada para planos core, mantendo guardrails ativos e auditáveis.</p>
|
||||
</header>
|
||||
|
||||
<div class="section">
|
||||
<h2>1. Fundamentos do Domínio</h2>
|
||||
<p>Billing define recursos e limites do produto. Não é camada de UI. É camada estrutural.</p>
|
||||
<div class="rule"><strong>Princípio:</strong> Role (RBAC) ≠ Plano (Billing). Plano dirige features e limites; role dirige acesso.</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>2. Planos Core (MVP)</h2>
|
||||
<ul>
|
||||
<li>clinic_free</li>
|
||||
<li>clinic_pro</li>
|
||||
<li>therapist_free</li>
|
||||
<li>therapist_pro</li>
|
||||
</ul>
|
||||
<div class="ok"><strong>Política:</strong> Planos core são estruturalmente protegidos por triggers.</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>3. Governança de Guardrails</h2>
|
||||
<ul>
|
||||
<li>Impedem alterar key de plano core</li>
|
||||
<li>Impedem alterar target de plano core</li>
|
||||
<li>Impedem deletar plano com subscription ativa</li>
|
||||
</ul>
|
||||
<div class="danger"><strong>Proibido:</strong> desabilitar triggers diretamente em produção.</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>4. Procedimento Oficial de Correção de Plano Core</h2>
|
||||
<p>Correções estruturais devem ocorrer via função administrativa controlada.</p>
|
||||
|
||||
<pre>
|
||||
create or replace function admin_fix_plan_target(
|
||||
p_plan_key text,
|
||||
p_new_target text
|
||||
) returns void
|
||||
language plpgsql
|
||||
security definer
|
||||
as $$
|
||||
declare
|
||||
v_plan_id uuid;
|
||||
begin
|
||||
select id into v_plan_id
|
||||
from plans
|
||||
where key = p_plan_key
|
||||
for update;
|
||||
|
||||
if v_plan_id is null then
|
||||
raise exception 'Plano não encontrado.';
|
||||
end if;
|
||||
|
||||
if exists (
|
||||
select 1 from subscriptions where plan_id = v_plan_id
|
||||
) then
|
||||
raise exception 'Plano possui subscriptions ativas.';
|
||||
end if;
|
||||
|
||||
update plans
|
||||
set target = p_new_target
|
||||
where id = v_plan_id;
|
||||
|
||||
end;
|
||||
$$;
|
||||
</pre>
|
||||
|
||||
<div class="warn">
|
||||
Esta função deve ser executada apenas por role administrativa e registrada em log de auditoria.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>5. Entitlements (Contrato Oficial)</h2>
|
||||
<p>Entitlements são derivados exclusivamente de <code>plan_features</code>.</p>
|
||||
<pre>
|
||||
plan_features (
|
||||
plan_id uuid,
|
||||
feature_id uuid,
|
||||
enabled boolean,
|
||||
limits jsonb
|
||||
)
|
||||
</pre>
|
||||
<div class="rule">
|
||||
Formato oficial de limits:
|
||||
{"max": 30}
|
||||
{"per_month": 40}
|
||||
{"max_users": 1}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>6. Preço Vigente</h2>
|
||||
<pre>
|
||||
create unique index uq_plan_price_active
|
||||
on plan_prices (plan_id, interval, currency)
|
||||
where is_active = true and active_to is null;
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>7. Onboarding</h2>
|
||||
<ul>
|
||||
<li>Tenant clinic → clinic_free</li>
|
||||
<li>Tenant therapist → therapist_free</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
Agência PSI — Billing Arquitetura Oficial v1.1
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
957
docs/planos/dev-documentacao-planos-seeder-v1.html
Normal file
957
docs/planos/dev-documentacao-planos-seeder-v1.html
Normal file
@@ -0,0 +1,957 @@
|
||||
<!doctype html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Documentação Interna — Planos, Assinaturas e Seeder (Billing) | Agência PSI</title>
|
||||
<style>
|
||||
:root{
|
||||
--bg0:#f6f8fc;
|
||||
--bg1:#eef2f8;
|
||||
--panel:rgba(255,255,255,.78);
|
||||
--panel2:rgba(255,255,255,.92);
|
||||
--border:rgba(15,23,42,.10);
|
||||
--text:rgba(15,23,42,.92);
|
||||
--muted:rgba(15,23,42,.70);
|
||||
--muted2:rgba(15,23,42,.56);
|
||||
--accent:#2563eb;
|
||||
--warn:#b45309;
|
||||
--danger:#b91c1c;
|
||||
--ok:#047857;
|
||||
--shadow: 0 18px 60px rgba(2,6,23,.10);
|
||||
--radius: 16px;
|
||||
--radius2: 22px;
|
||||
--mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
||||
--sans: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial;
|
||||
}
|
||||
*{ box-sizing:border-box; }
|
||||
body{
|
||||
margin:0;
|
||||
font-family: var(--sans);
|
||||
color: var(--text);
|
||||
background:
|
||||
radial-gradient(1200px 600px at 18% -10%, rgba(37,99,235,.10) 0%, transparent 60%),
|
||||
radial-gradient(900px 520px at 90% 10%, rgba(2,132,199,.10) 0%, transparent 55%),
|
||||
linear-gradient(180deg, var(--bg0), var(--bg1));
|
||||
}
|
||||
.layout{
|
||||
display:grid;
|
||||
grid-template-columns: 320px 1fr;
|
||||
gap: 20px;
|
||||
max-width: 1320px;
|
||||
margin: 0 auto;
|
||||
padding: 28px 18px 42px;
|
||||
}
|
||||
header{
|
||||
grid-column: 1 / -1;
|
||||
padding: 18px;
|
||||
border: 1px solid var(--border);
|
||||
background: linear-gradient(180deg, rgba(255,255,255,.92), rgba(255,255,255,.72));
|
||||
border-radius: var(--radius2);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.kicker{
|
||||
font-size:12px;
|
||||
letter-spacing:.08em;
|
||||
text-transform:uppercase;
|
||||
color:var(--muted2);
|
||||
margin:0 0 8px;
|
||||
}
|
||||
h1{
|
||||
margin:0 0 8px;
|
||||
font-size:30px;
|
||||
letter-spacing:-0.02em;
|
||||
}
|
||||
.subtitle{
|
||||
margin:0;
|
||||
color:var(--muted);
|
||||
max-width:980px;
|
||||
line-height:1.55;
|
||||
font-size:14px;
|
||||
}
|
||||
aside{
|
||||
position:sticky;
|
||||
top:18px;
|
||||
align-self:start;
|
||||
border:1px solid var(--border);
|
||||
background:var(--panel2);
|
||||
border-radius:var(--radius);
|
||||
box-shadow:var(--shadow);
|
||||
overflow:hidden;
|
||||
}
|
||||
.toc-head{
|
||||
padding:14px;
|
||||
border-bottom:1px solid var(--border);
|
||||
background:rgba(15,23,42,.02);
|
||||
}
|
||||
.toc-title{ margin:0 0 6px; font-weight:800; }
|
||||
.toc-desc{ margin:0; font-size:12px; color:var(--muted); }
|
||||
.toc{ padding:10px; }
|
||||
.toc a{
|
||||
display:block;
|
||||
padding:8px 10px;
|
||||
border-radius:12px;
|
||||
font-size:13px;
|
||||
color:rgba(15,23,42,.88);
|
||||
text-decoration:none;
|
||||
}
|
||||
.toc a:hover{ background:rgba(37,99,235,.06); }
|
||||
main{
|
||||
border:1px solid var(--border);
|
||||
background:var(--panel);
|
||||
backdrop-filter: blur(6px);
|
||||
border-radius:var(--radius);
|
||||
box-shadow:var(--shadow);
|
||||
overflow:hidden;
|
||||
}
|
||||
.section{ padding:18px; border-top:1px solid var(--border); }
|
||||
.section:first-child{ border-top:none; }
|
||||
h2{ margin:0 0 10px; font-size:18px; }
|
||||
h3{ margin:12px 0 8px; font-size:14px; color:rgba(15,23,42,.86); letter-spacing:.01em; }
|
||||
p{ margin:0 0 10px; color:var(--muted); line-height:1.65; }
|
||||
ul{ margin:10px 0 0 18px; color:var(--muted); }
|
||||
li{ margin:6px 0; }
|
||||
.card{
|
||||
border:1px solid var(--border);
|
||||
background:rgba(255,255,255,.72);
|
||||
border-radius:var(--radius);
|
||||
padding:14px;
|
||||
}
|
||||
.rule{
|
||||
border-left:4px solid var(--accent);
|
||||
background:rgba(37,99,235,.08);
|
||||
padding:12px;
|
||||
border-radius:12px;
|
||||
margin:12px 0;
|
||||
color: rgba(15,23,42,.82);
|
||||
}
|
||||
.ok{
|
||||
border-left:4px solid var(--ok);
|
||||
background:rgba(4,120,87,.08);
|
||||
padding:12px;
|
||||
border-radius:12px;
|
||||
margin:12px 0;
|
||||
color: rgba(15,23,42,.82);
|
||||
}
|
||||
.warn{
|
||||
border-left:4px solid var(--warn);
|
||||
background:rgba(180,83,9,.10);
|
||||
padding:12px;
|
||||
border-radius:12px;
|
||||
margin:12px 0;
|
||||
color: rgba(15,23,42,.82);
|
||||
}
|
||||
.danger{
|
||||
border-left:4px solid var(--danger);
|
||||
background:rgba(185,28,28,.08);
|
||||
padding:12px;
|
||||
border-radius:12px;
|
||||
margin:12px 0;
|
||||
color: rgba(15,23,42,.82);
|
||||
}
|
||||
.table{
|
||||
width:100%;
|
||||
border-collapse:separate;
|
||||
border-spacing:0;
|
||||
margin-top:10px;
|
||||
border:1px solid var(--border);
|
||||
border-radius:var(--radius);
|
||||
overflow:hidden;
|
||||
background: rgba(255,255,255,.72);
|
||||
}
|
||||
.table th, .table td{
|
||||
padding:10px 12px;
|
||||
border-bottom:1px solid rgba(15,23,42,.08);
|
||||
font-size:13px;
|
||||
color:rgba(15,23,42,.88);
|
||||
vertical-align: top;
|
||||
}
|
||||
.table th{
|
||||
background:rgba(15,23,42,.03);
|
||||
font-weight:800;
|
||||
color: rgba(15,23,42,.72);
|
||||
}
|
||||
.table tr:last-child td{ border-bottom:none; }
|
||||
code, pre{ font-family:var(--mono); font-size:12px; }
|
||||
pre{
|
||||
background:rgba(2,6,23,.04);
|
||||
border:1px solid var(--border);
|
||||
border-radius:var(--radius);
|
||||
padding:12px;
|
||||
margin-top:10px;
|
||||
overflow:auto;
|
||||
line-height: 1.55;
|
||||
color: rgba(15,23,42,.90);
|
||||
}
|
||||
.pill{
|
||||
display:inline-block;
|
||||
padding:6px 10px;
|
||||
border-radius:999px;
|
||||
border:1px solid var(--border);
|
||||
background:rgba(255,255,255,.72);
|
||||
font-size:12px;
|
||||
margin:4px 6px 0 0;
|
||||
color: rgba(15,23,42,.78);
|
||||
}
|
||||
.grid2{
|
||||
display:grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
.kv{
|
||||
display:flex;
|
||||
gap:10px;
|
||||
align-items:flex-start;
|
||||
justify-content:space-between;
|
||||
border:1px solid rgba(15,23,42,.08);
|
||||
background:rgba(255,255,255,.72);
|
||||
border-radius:14px;
|
||||
padding:12px;
|
||||
}
|
||||
.kv b{ color: rgba(15,23,42,.88); }
|
||||
.kv span{ color: var(--muted); font-size:12px; }
|
||||
.path{
|
||||
display:inline-block;
|
||||
padding:3px 8px;
|
||||
border-radius:10px;
|
||||
border:1px solid rgba(15,23,42,.12);
|
||||
background:rgba(255,255,255,.72);
|
||||
color:rgba(15,23,42,.88);
|
||||
font-family:var(--mono);
|
||||
font-size:12px;
|
||||
margin:2px 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
footer{
|
||||
grid-column:1 / -1;
|
||||
margin-top:14px;
|
||||
text-align:center;
|
||||
font-size:12px;
|
||||
color:var(--muted2);
|
||||
}
|
||||
@media (max-width: 980px){
|
||||
.layout{ grid-template-columns:1fr; }
|
||||
aside{ position:relative; top:0; }
|
||||
.grid2{ grid-template-columns: 1fr; }
|
||||
}
|
||||
@media print{
|
||||
header, aside, main{ box-shadow:none; }
|
||||
.section{ page-break-inside:avoid; }
|
||||
body{ background:white; }
|
||||
main, aside{ background:white; }
|
||||
.rule,.ok,.warn,.danger{ background:white; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="layout">
|
||||
|
||||
<header>
|
||||
<p class="kicker">Agência PSI • Documento interno</p>
|
||||
<h1>Planos, Assinaturas e Seeder — Billing (MVP)</h1>
|
||||
<p class="subtitle">
|
||||
Documentação interna do <strong>domínio de Billing</strong> do SaaS multi-tenant (Agência PSI),
|
||||
cobrindo <strong>modelo de dados</strong>, <strong>views oficiais</strong>, <strong>catálogo de planos</strong>,
|
||||
<strong>princípios de produto</strong> e um <strong>seeder idempotente</strong> para instalação nova.
|
||||
O objetivo é impedir divergência entre <em>UI</em>, <em>backend</em> e <em>banco</em> (e evitar pricing nulo, upgrade quebrado e gating inconsistente).<br><br><strong>Atualizado em:</strong> 2026-03-01 (após validações reais do schema e execução do seeder).
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<aside>
|
||||
<div class="toc-head">
|
||||
<div class="toc-title">Sumário</div>
|
||||
<p class="toc-desc">Navegação rápida entre as seções.</p>
|
||||
</div>
|
||||
<nav class="toc">
|
||||
<a href="#1-visao-geral">1. Visão geral do domínio</a>
|
||||
<a href="#2-principios">2. Princípios e decisões</a>
|
||||
<a href="#3-conceitos">3. Conceitos: role vs target vs plano vs feature</a>
|
||||
<a href="#4-modelo">4. Modelo de dados (Postgres/Supabase)</a>
|
||||
<a href="#5-views">5. Views oficiais (fonte de verdade)</a>
|
||||
<a href="#6-catalogo">6. Catálogo de Planos (MVP)</a>
|
||||
<a href="#7-precos">7. Preços (MVP) e vigência</a>
|
||||
<a href="#8-seeder">8. Seeder (nova instalação) — SQL idempotente</a>
|
||||
<a href="#9-onboarding">9. Onboarding & Upgrade (fluxo)</a>
|
||||
<a href="#10-runbook">10. Operação (runbook rápido)</a>
|
||||
<a href="#11-qa">11. Checklist de QA</a>
|
||||
<a href="#12-prompt">12. Prompt Mestre — continuação (Billing)</a>
|
||||
<a href="#13-tags">Tags</a>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<main>
|
||||
|
||||
<section class="section" id="1-visao-geral">
|
||||
<h2>1. Visão geral do domínio</h2>
|
||||
<p>
|
||||
O Billing define <strong>o que pode</strong> e <strong>o quanto pode</strong> dentro do produto.
|
||||
Ele não é “uma tela de preço”: é a camada que decide
|
||||
<strong>limites</strong> (quantidade), <strong>habilitações</strong> (booleanos) e <strong>estado de assinatura</strong>.
|
||||
</p>
|
||||
<div class="rule">
|
||||
<strong>Definição operacional:</strong> o Billing é composto por (1) catálogo de planos, (2) preços vigentes,
|
||||
(3) assinatura ativa por tenant/usuário, e (4) entitlements derivados do plano.
|
||||
</div>
|
||||
<div class="ok">
|
||||
<strong>Objetivo do MVP:</strong> todo mundo começa no <strong>FREE</strong> (clínica e terapeuta).
|
||||
Paciente não é pagante; o “portal do paciente” é um recurso habilitado pelo plano do terapeuta/clínica.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="2-principios">
|
||||
<h2>2. Princípios e decisões</h2>
|
||||
<ul>
|
||||
<li><strong>Separação rígida</strong>: Role (RBAC) não é Plano (Billing). Plano define recursos; role define permissões de acesso.</li>
|
||||
<li><strong>Planos por target</strong>: existe plano de <code>clinic</code> e plano de <code>therapist</code>. Isso impede aplicar plano errado em outro tipo de conta.</li>
|
||||
<li><strong>Tudo começa gratuito</strong>: criação de tenant atribui automaticamente um plano <code>*_free</code>.</li>
|
||||
<li><strong>Pricing público por View</strong>: a UI de preços deve consumir <code>v_public_pricing</code> (não montar preço manual no front).</li>
|
||||
<li><strong>Preço é temporal</strong>: preço tem vigência (<code>active_from</code>/<code>active_to</code>) e um “ativo atual”.</li>
|
||||
<li><strong>Seeder é padrão</strong>: nova instalação do banco deve nascer com os 4 planos do MVP + public metadata + preços PRO.</li>
|
||||
</ul>
|
||||
<div class="warn">
|
||||
<strong>Problema real observado:</strong> a view <code>v_public_pricing</code> retornou preços <code>null</code> porque havia histórico em <code>plan_prices</code> mas nenhum registro vigente (todos com <code>is_active=false</code> e <code>active_to</code> preenchido).
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="3-conceitos">
|
||||
<h2>3. Conceitos: role vs target vs plano vs feature</h2>
|
||||
|
||||
<div class="card">
|
||||
<div class="grid2">
|
||||
<div class="kv"><b>Role (RBAC)</b><span>permissão de UI/rotas (clinic_admin, therapist, patient etc.)</span></div>
|
||||
<div class="kv"><b>Target (produto)</b><span>tipo de conta: <code>clinic</code> ou <code>therapist</code></span></div>
|
||||
<div class="kv"><b>Plano (billing)</b><span>free/pro por target; é o “pacote” contratado</span></div>
|
||||
<div class="kv"><b>Feature / Limite</b><span>entitlements: booleanos e limites numéricos derivados do plano</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>3.1 Regra do produto: “um usuário pode ser paciente e terapeuta”</h3>
|
||||
<p>
|
||||
Essa regra é de <strong>identidade</strong> (um mesmo <em>user</em> pode estar em múltiplos contextos),
|
||||
mas o plano é aplicado ao <strong>tenant</strong> (clínica/terapeuta). Assim, um usuário pode:
|
||||
</p>
|
||||
<ul>
|
||||
<li>estar em um tenant therapist (com <code>therapist_free/pro</code>)</li>
|
||||
<li>estar em um tenant clinic (com <code>clinic_free/pro</code>)</li>
|
||||
<li>acessar portal de paciente como consumidor do serviço (sem plano próprio)</li>
|
||||
</ul>
|
||||
|
||||
<div class="rule">
|
||||
<strong>Consequência:</strong> plano nunca deve ser inferido do role.
|
||||
O role dirige menus/rotas; o plano dirige features/limites.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="4-modelo">
|
||||
<h2>4. Modelo de dados (Postgres/Supabase)</h2>
|
||||
|
||||
<h3>4.1 Tabelas mapeadas (schema: public)</h3>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Tabela</th>
|
||||
<th>Responsabilidade</th>
|
||||
<th>Observações práticas</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>plans</code></td>
|
||||
<td>Catálogo interno de planos (id, <code>key</code>, <code>target</code>, flags e campos legados de preço)</td>
|
||||
<td><strong>Não</strong> usar <code>plans.price_cents</code> como preço público; é legado/fallback.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>plan_prices</code></td>
|
||||
<td>Preços por intervalo e moeda, com vigência</td>
|
||||
<td>Fonte do valor monetário; a view pública agrega mensal/anual.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>plan_features</code></td>
|
||||
<td>Entitlements por plano (limites e habilitações)</td>
|
||||
<td>Define o que o produto permite no runtime (gating).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>plan_public</code></td>
|
||||
<td>Marketing/metadata do plano (nome público, descrição, badge, destaque, visibilidade)</td>
|
||||
<td>Direciona a tela de preços e o “tom” comercial.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>plan_public_bullets</code></td>
|
||||
<td>Bullets de venda por plano</td>
|
||||
<td>Lista simples; a view pode agregá-las em array.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>subscriptions</code></td>
|
||||
<td>Assinatura ativa (por tenant ou user) e status</td>
|
||||
<td>Fonte de verdade do plano vigente do tenant.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>subscription_events</code></td>
|
||||
<td>Histórico de mudanças (old/new plan)</td>
|
||||
<td>Útil para auditoria e debug de upgrades.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>subscription_intents</code></td>
|
||||
<td>Intenção/checkout pendente</td>
|
||||
<td>Controla upgrade antes de virar subscription.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>entitlements_invalidation</code></td>
|
||||
<td>Invalidação de cache de entitlements</td>
|
||||
<td>Garante refresh quando plano muda.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>4.2 Padrão de “preço vigente”</h3>
|
||||
<div class="warn">
|
||||
<strong>Armada clássica:</strong> se não existir pelo menos 1 preço vigente por <code>(plan_id, interval, currency)</code>,
|
||||
a tela de pricing pode retornar <code>null</code> e o checkout fica sem referência.
|
||||
</div>
|
||||
|
||||
<pre><code>-- Um preço é considerado vigente quando:
|
||||
-- is_active = true
|
||||
-- AND active_to IS NULL
|
||||
-- AND now() >= active_from (se active_from existir)
|
||||
</code></pre>
|
||||
</section>
|
||||
|
||||
<section class="section" id="5-views">
|
||||
<h2>5. Views oficiais (fonte de verdade)</h2>
|
||||
|
||||
<h3>5.1 View pública de pricing (UI deve consumir)</h3>
|
||||
<div class="rule">
|
||||
<strong>UI MUST:</strong> a tela de preços deve consultar <code>v_public_pricing</code>.
|
||||
Evitar compor preços no front com join manual, pois isso cria divergência e bugs silenciosos.
|
||||
</div>
|
||||
<pre><code>select
|
||||
plan_key,
|
||||
plan_name,
|
||||
public_name,
|
||||
public_description,
|
||||
badge,
|
||||
is_featured,
|
||||
is_visible,
|
||||
sort_order,
|
||||
monthly_cents,
|
||||
yearly_cents,
|
||||
monthly_currency,
|
||||
yearly_currency,
|
||||
bullets,
|
||||
plan_target
|
||||
from v_public_pricing
|
||||
order by plan_target, sort_order;</code></pre>
|
||||
|
||||
<h3>5.2 View de preços ativos (infra/diagnóstico)</h3>
|
||||
<pre><code>select *
|
||||
from v_plan_active_prices
|
||||
order by plan_id;</code></pre>
|
||||
|
||||
<h3>5.3 View de assinatura do tenant (gating/RBAC por plano)</h3>
|
||||
<pre><code>select *
|
||||
from v_tenant_active_subscription;</code></pre>
|
||||
|
||||
<h3>5.4 View de saúde de assinaturas (debug)</h3>
|
||||
<pre><code>select *
|
||||
from v_subscription_health
|
||||
where status <> 'healthy';</code></pre>
|
||||
</section>
|
||||
|
||||
<section class="section" id="6-catalogo">
|
||||
<h2>6. Catálogo de Planos (MVP)</h2>
|
||||
|
||||
<div class="ok">
|
||||
<strong>Decisão fechada:</strong> MVP com 4 planos (2 targets × free/pro).
|
||||
Os planos antigos (ex.: <code>pro</code>, <code>plano_2</code>) podem ser descontinuados e ficar invisíveis.
|
||||
</div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>plan_key</th>
|
||||
<th>target</th>
|
||||
<th>Tipo</th>
|
||||
<th>Objetivo</th>
|
||||
<th>Notas de produto</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>clinic_free</code></td>
|
||||
<td><code>clinic</code></td>
|
||||
<td>FREE</td>
|
||||
<td>Entrada de clínicas pequenas (começar sem cartão)</td>
|
||||
<td>Usável, mas com teto claro para gerar upgrade natural.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>clinic_pro</code></td>
|
||||
<td><code>clinic</code></td>
|
||||
<td>PRO</td>
|
||||
<td>Clínica completa</td>
|
||||
<td>Habilita secretária, relatórios, automações etc. (conforme evolução).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>therapist_free</code></td>
|
||||
<td><code>therapist</code></td>
|
||||
<td>FREE</td>
|
||||
<td>Entrada de terapeuta solo</td>
|
||||
<td>Permite operar, mas limita escala (pacientes/sessões).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>therapist_pro</code></td>
|
||||
<td><code>therapist</code></td>
|
||||
<td>PRO</td>
|
||||
<td>Profissional estabelecido</td>
|
||||
<td>Expande limites e libera automações/relatórios conforme roadmap.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>6.1 Limites sugeridos (MVP — ajustável)</h3>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Entitlement</th>
|
||||
<th>clinic_free</th>
|
||||
<th>clinic_pro</th>
|
||||
<th>therapist_free</th>
|
||||
<th>therapist_pro</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>therapists_limit</code></td>
|
||||
<td>1</td>
|
||||
<td>ilimitado</td>
|
||||
<td>—</td>
|
||||
<td>—</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>patients_limit</code></td>
|
||||
<td>30</td>
|
||||
<td>ilimitado</td>
|
||||
<td>10</td>
|
||||
<td>ilimitado</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>sessions_month_limit</code></td>
|
||||
<td>100</td>
|
||||
<td>ilimitado</td>
|
||||
<td>40</td>
|
||||
<td>ilimitado</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>secretary_enabled</code></td>
|
||||
<td>false</td>
|
||||
<td>true</td>
|
||||
<td>—</td>
|
||||
<td>—</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>reports_enabled</code></td>
|
||||
<td>false</td>
|
||||
<td>true</td>
|
||||
<td>false</td>
|
||||
<td>true</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>reminders_enabled</code></td>
|
||||
<td>false</td>
|
||||
<td>true</td>
|
||||
<td>false</td>
|
||||
<td>true</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>patient_portal_enabled</code></td>
|
||||
<td>true</td>
|
||||
<td>true</td>
|
||||
<td>true</td>
|
||||
<td>true</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="warn">
|
||||
<strong>Observação:</strong> nomes de entitlements dependem da sua tabela de <code>features</code> (se existir).
|
||||
A lógica do seeder abaixo separa “chaves sugeridas” da implementação final.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="7-precos">
|
||||
<h2>7. Preços (MVP) e vigência</h2>
|
||||
|
||||
<h3>7.1 Preços sugeridos</h3>
|
||||
<div class="card">
|
||||
<ul>
|
||||
<li><strong>clinic_free</strong>: Grátis (sem preço, ou <code>0</code> se o front exigir número)</li>
|
||||
<li><strong>clinic_pro</strong>: mensal R$ 149 (<code>14900</code>), anual R$ 1490 (<code>149000</code>)</li>
|
||||
<li><strong>therapist_free</strong>: Grátis (sem preço, ou <code>0</code>)</li>
|
||||
<li><strong>therapist_pro</strong>: mensal R$ 49 (<code>4900</code>), anual R$ 490 (<code>49000</code>)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h3>7.2 Regras de vigência</h3>
|
||||
<div class="rule">
|
||||
<strong>Regra recomendada:</strong> 1 preço vigente por <code>(plan_id, interval, currency)</code>.
|
||||
Para prevenir inconsistência, criar índice único parcial.
|
||||
</div>
|
||||
|
||||
<pre><code>create unique index if not exists uq_plan_price_active
|
||||
on plan_prices (plan_id, interval, currency)
|
||||
where is_active = true and active_to is null;</code></pre>
|
||||
|
||||
<div class="danger">
|
||||
<strong>Anti-padrão:</strong> encerrar todos preços e esquecer de inserir os novos. Resultado: <code>v_public_pricing</code> com <code>null</code>.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="8-seeder">
|
||||
<h2>8. Seeder (nova instalação) — SQL idempotente</h2>
|
||||
|
||||
<div class="ok">
|
||||
<strong>Objetivo do seeder:</strong> instalar (1) planos, (2) metadata pública, (3) bullets, (4) preços PRO vigentes
|
||||
e (opcional) (5) entitlements iniciais.
|
||||
O script deve ser <strong>idempotente</strong>: rodar várias vezes sem duplicar registros.
|
||||
</div>
|
||||
|
||||
<h3>8.1 Convenções do seeder</h3>
|
||||
<ul>
|
||||
<li>Usar <code>plans.key</code> como chave estável (única). A view pública expõe isso como <code>plan_key</code>.</li>
|
||||
<li>Para inserts, preferir <code>insert ... on conflict ... do update</code> quando houver unique constraint.</li>
|
||||
<li>Para preços: encerrar preço vigente anterior e inserir um novo (ou atualizar, conforme sua política).</li>
|
||||
<li>Manter <code>source='manual'</code> no MVP (provider pode entrar depois com Stripe).</li>
|
||||
</ul>
|
||||
|
||||
<h3>8.2 Seeder completo (MVP)</h3>
|
||||
<pre><code>-- ============================================================
|
||||
-- SEEDER — BILLING (MVP) • SCHEMA REAL (confirmado)
|
||||
-- Planos finais: clinic_free, clinic_pro, therapist_free, therapist_pro
|
||||
-- Observação: v_public_pricing expõe (plan_key/plan_target), mas na tabela base é (plans.key / plans.target).
|
||||
-- ============================================================
|
||||
|
||||
-- 0) Proteção: 1 preço vigente por (plan_id, interval, currency)
|
||||
create unique index if not exists uq_plan_price_active
|
||||
on plan_prices (plan_id, interval, currency)
|
||||
where is_active = true and active_to is null;
|
||||
|
||||
-- 1) Plans (public.plans) — usa colunas reais: key, name, target
|
||||
insert into plans (key, name, description, is_active, price_cents, currency, billing_interval, target)
|
||||
values
|
||||
('clinic_free', 'CLINIC FREE', 'Plano gratuito para clínicas iniciarem.', true, 0, 'BRL', 'month', 'clinic'),
|
||||
('clinic_pro', 'CLINIC PRO', 'Plano completo para clínicas.', true, 14900, 'BRL', 'month', 'clinic'),
|
||||
('therapist_free', 'THERAPIST FREE', 'Plano gratuito para terapeutas.', true, 0, 'BRL', 'month', 'therapist'),
|
||||
('therapist_pro', 'THERAPIST PRO', 'Plano completo para terapeutas.', true, 4900, 'BRL', 'month', 'therapist')
|
||||
on conflict (key) do update
|
||||
set name = excluded.name,
|
||||
description = excluded.description,
|
||||
is_active = excluded.is_active,
|
||||
price_cents = excluded.price_cents,
|
||||
currency = excluded.currency,
|
||||
billing_interval = excluded.billing_interval,
|
||||
target = excluded.target;
|
||||
|
||||
-- 2) Plan public (public.plan_public) — metadata de pricing
|
||||
with p as (
|
||||
select id, key from plans
|
||||
where key in ('clinic_free','clinic_pro','therapist_free','therapist_pro')
|
||||
)
|
||||
insert into plan_public (plan_id, public_name, public_description, badge, is_featured, is_visible, sort_order)
|
||||
select
|
||||
id,
|
||||
case key
|
||||
when 'clinic_free' then 'Clínica — Free'
|
||||
when 'clinic_pro' then 'Clínica — PRO'
|
||||
when 'therapist_free' then 'Terapeuta — Free'
|
||||
when 'therapist_pro' then 'Terapeuta — PRO'
|
||||
end,
|
||||
case key
|
||||
when 'clinic_free' then 'Para clínicas pequenas começarem sem cartão.'
|
||||
when 'clinic_pro' then 'Para clínicas que querem recursos completos.'
|
||||
when 'therapist_free' then 'Para começar e organizar sua prática.'
|
||||
when 'therapist_pro' then 'Para expandir com automações e escala.'
|
||||
end,
|
||||
case key
|
||||
when 'clinic_free' then 'Grátis'
|
||||
when 'therapist_free' then 'Grátis'
|
||||
else null
|
||||
end,
|
||||
case key
|
||||
when 'clinic_pro' then true
|
||||
when 'therapist_pro' then true
|
||||
else false
|
||||
end,
|
||||
true,
|
||||
case key
|
||||
when 'clinic_free' then 10
|
||||
when 'clinic_pro' then 20
|
||||
when 'therapist_free' then 10
|
||||
when 'therapist_pro' then 20
|
||||
end
|
||||
from p
|
||||
on conflict (plan_id) do update
|
||||
set public_name = excluded.public_name,
|
||||
public_description = excluded.public_description,
|
||||
badge = excluded.badge,
|
||||
is_featured = excluded.is_featured,
|
||||
is_visible = excluded.is_visible,
|
||||
sort_order = excluded.sort_order;
|
||||
|
||||
-- 3) Bullets (public.plan_public_bullets) — reset simples para MVP
|
||||
delete from plan_public_bullets
|
||||
where plan_id in (select id from plans where key in ('clinic_free','clinic_pro','therapist_free','therapist_pro'));
|
||||
|
||||
insert into plan_public_bullets (plan_id, text, highlight, sort_order)
|
||||
values
|
||||
((select id from plans where key='clinic_free'), '1 terapeuta incluído', false, 10),
|
||||
((select id from plans where key='clinic_free'), 'Até 30 pacientes', false, 20),
|
||||
((select id from plans where key='clinic_free'), 'Até 100 sessões/mês', false, 30),
|
||||
|
||||
((select id from plans where key='clinic_pro'), 'Terapeutas ilimitados', true, 10),
|
||||
((select id from plans where key='clinic_pro'), 'Pacientes ilimitados', true, 20),
|
||||
((select id from plans where key='clinic_pro'), 'Relatórios e lembretes', false, 30),
|
||||
|
||||
((select id from plans where key='therapist_free'), 'Até 10 pacientes', false, 10),
|
||||
((select id from plans where key='therapist_free'), 'Até 40 sessões/mês', false, 20),
|
||||
((select id from plans where key='therapist_free'), 'Portal do paciente', false, 30),
|
||||
|
||||
((select id from plans where key='therapist_pro'), 'Pacientes ilimitados', true, 10),
|
||||
((select id from plans where key='therapist_pro'), 'Sessões ilimitadas', true, 20),
|
||||
((select id from plans where key='therapist_pro'), 'Relatórios e lembretes', false, 30);
|
||||
|
||||
-- 4) Preços vigentes (public.plan_prices) — somente PRO
|
||||
do $$
|
||||
declare
|
||||
v_clinic_pro uuid;
|
||||
v_therapist_pro uuid;
|
||||
begin
|
||||
select id into v_clinic_pro from plans where key='clinic_pro';
|
||||
select id into v_therapist_pro from plans where key='therapist_pro';
|
||||
|
||||
update plan_prices
|
||||
set is_active = false, active_to = now()
|
||||
where plan_id in (v_clinic_pro, v_therapist_pro)
|
||||
and is_active = true
|
||||
and active_to is null;
|
||||
|
||||
insert into plan_prices (plan_id, currency, interval, amount_cents, is_active, active_from, active_to, source, provider, provider_price_id)
|
||||
values
|
||||
(v_clinic_pro, 'BRL', 'month', 14900, true, now(), null, 'manual', null, null),
|
||||
(v_clinic_pro, 'BRL', 'year', 149000, true, now(), null, 'manual', null, null),
|
||||
(v_therapist_pro, 'BRL', 'month', 4900, true, now(), null, 'manual', null, null),
|
||||
(v_therapist_pro, 'BRL', 'year', 49000, true, now(), null, 'manual', null, null);
|
||||
exception
|
||||
when unique_violation then
|
||||
raise notice 'Preço vigente já existe para algum (plan_id, interval, currency).';
|
||||
end $$;
|
||||
|
||||
-- 5) (Opcional) Integridade: impedir apagar plano em uso
|
||||
-- A FK subscriptions.plan_id -> plans.id deve estar com ON DELETE RESTRICT.
|
||||
-- Se precisar aplicar:
|
||||
-- alter table public.subscriptions drop constraint if exists subscriptions_plan_id_fkey;
|
||||
-- alter table public.subscriptions add constraint subscriptions_plan_id_fkey
|
||||
-- foreign key (plan_id) references public.plans(id) on delete restrict;
|
||||
|
||||
-- 6) Validação final (deve retornar 4 planos visíveis)
|
||||
select plan_key, plan_name, plan_target, monthly_cents, yearly_cents
|
||||
from v_public_pricing
|
||||
where is_visible = true
|
||||
order by plan_target, sort_order, plan_key;</code></pre>
|
||||
|
||||
<div class="warn">
|
||||
<strong>Nota de adaptação:</strong> o seeder acima assume certas colunas (ex.: <code>plans.plan_key</code>, <code>plans.plan_target</code>, <code>plans.is_active</code>, <code>plan_public.*</code>).
|
||||
Se o seu schema tiver nomes diferentes, ajuste no primeiro uso e depois mantenha como “padrão oficial”.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<section class="section" id="8b-entitlements">
|
||||
<h2>8B. Entitlements — Schema real (plan_features)</h2>
|
||||
<p>
|
||||
O MVP usa <code>plan_features</code> como tabela de ligação entre plano e feature. O schema confirmado é:
|
||||
<code>(plan_id uuid NOT NULL, feature_id uuid NOT NULL, enabled boolean NOT NULL default true, limits jsonb NULL)</code>.
|
||||
</p>
|
||||
<div class="rule">
|
||||
<strong>Padrão recomendado para limits (jsonb):</strong> padronizar chaves por tipo de limite para evitar ambiguidade no front/back.
|
||||
Sugestão:
|
||||
<code>{"max": 30}</code> (limite absoluto),
|
||||
<code>{"per_month": 40}</code> (por período),
|
||||
<code>{"max_users": 1}</code> (limite de assentos),
|
||||
e manter <code>enabled</code> como flag binária.
|
||||
</div>
|
||||
<div class="warn">
|
||||
<strong>Pré-requisito:</strong> para seedar entitlements, é necessário listar/definir as features na tabela de features (ex.: <code>features</code>).
|
||||
Este documento mantém os limites do MVP como referência de produto; o seeder de <code>plan_features</code> deve mapear essas chaves para <code>feature_id</code> reais.
|
||||
</div>
|
||||
|
||||
<h3>Template (exemplo) — como gravar limites</h3>
|
||||
<pre><code>-- Exemplo: habilitar feature X com limite max=30 para clinic_free
|
||||
insert into plan_features (plan_id, feature_id, enabled, limits)
|
||||
values (
|
||||
(select id from plans where key='clinic_free'),
|
||||
'FEATURE_UUID_AQUI',
|
||||
true,
|
||||
'{"max": 30}'::jsonb
|
||||
);</code></pre>
|
||||
</section>
|
||||
|
||||
<section class="section" id="8c-regras-negocio">
|
||||
<h2>8C. Regras de negócio confirmadas no banco</h2>
|
||||
<div class="ok">
|
||||
<strong>Regra confirmada:</strong> inserir subscription de <code>clinic_*</code> exige <code>tenant_id</code>.
|
||||
Em testes, uma tentativa de inserir assinatura de clínica sem tenant resultou em erro:
|
||||
<em>“Assinatura clinic exige tenant_id.”</em>
|
||||
</div>
|
||||
<div class="rule">
|
||||
<strong>Consequência:</strong> assinatura de clínica é “por tenant”; assinatura de terapeuta pode ser por <code>tenant_id</code> ou <code>user_id</code>,
|
||||
conforme sua arquitetura — mas o banco já impõe pelo menos o caso de clínica.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<section class="section" id="9-onboarding">
|
||||
<h2>9. Onboarding & Upgrade (fluxo)</h2>
|
||||
|
||||
<h3>9.1 Onboarding (criação de tenant)</h3>
|
||||
<ul>
|
||||
<li>Ao criar um tenant <code>clinic</code> → atribuir automaticamente <code>clinic_free</code>.</li>
|
||||
<li>Ao criar um tenant <code>therapist</code> → atribuir automaticamente <code>therapist_free</code>.</li>
|
||||
<li>O plano deve ser a fonte de verdade para habilitar recursos (entitlements store).</li>
|
||||
</ul>
|
||||
|
||||
<h3>9.2 Upgrade</h3>
|
||||
<div class="rule">
|
||||
Upgrade é troca de plano na assinatura: <code>*_free → *_pro</code>.
|
||||
O sistema deve invalidar entitlements e atualizar cache (via <code>entitlements_invalidation</code> ou mecanismo equivalente).
|
||||
</div>
|
||||
|
||||
<h3>9.3 Downgrade/expiração</h3>
|
||||
<p>
|
||||
No MVP, a regra segura é: ao expirar, <strong>bloquear novas criações premium</strong>,
|
||||
mas <strong>não apagar dados</strong>. Apenas retira capacidade.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="section" id="10-runbook">
|
||||
<h2>10. Operação (runbook rápido)</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>Incidente comum: Pricing mostra preços nulos</h3>
|
||||
<ol style="margin:10px 0 0 18px; color: var(--muted); line-height:1.65;">
|
||||
<li>Rodar <code>select * from v_public_pricing;</code></li>
|
||||
<li>Rodar <code>select * from plan_prices where plan_id = ... order by created_at desc;</code></li>
|
||||
<li>Confirmar existência de preço vigente: <code>is_active=true</code> e <code>active_to is null</code></li>
|
||||
<li>Se não existir, inserir preços PRO vigentes (month/year) e validar view novamente.</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="card" style="margin-top:12px;">
|
||||
<h3>Incidente comum: Plano aparece errado para um tenant</h3>
|
||||
<ol style="margin:10px 0 0 18px; color: var(--muted); line-height:1.65;">
|
||||
<li>Verificar <code>v_tenant_active_subscription</code> para o tenant em questão.</li>
|
||||
<li>Verificar se o plano tem <code>plan_target</code> correto.</li>
|
||||
<li>Verificar se o guard/menu não está inferindo plano do role (anti-padrão).</li>
|
||||
<li>Invalidar entitlements e reavaliar.</li>
|
||||
</ol>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="11-qa">
|
||||
<h2>11. Checklist de QA</h2>
|
||||
<ul>
|
||||
<li><strong>Seeder</strong>: rodar duas vezes e confirmar que não duplica registros.</li>
|
||||
<li><strong>Pricing</strong>: <code>v_public_pricing</code> retorna 4 planos, com preços preenchidos para PRO.</li>
|
||||
<li><strong>Upgrade</strong>: trocar plano e confirmar mudança de entitlements no runtime.</li>
|
||||
<li><strong>FREE</strong>: criação de tenant atribui automaticamente plano free correto.</li>
|
||||
<li><strong>Target</strong>: clínica nunca recebe plano therapist (e vice-versa).</li>
|
||||
<li><strong>Vigência</strong>: inserir novo preço e confirmar que o antigo foi encerrado (active_to preenchido).</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="section" id="12-prompt">
|
||||
<h2>12. Prompt Mestre — Continuidade do Billing (Planos/Assinaturas)</h2>
|
||||
|
||||
<div class="rule">
|
||||
Sempre que iniciar um novo chat sobre Billing/Planos, copie e cole este prompt.
|
||||
Ele representa o estado oficial do domínio e da estrutura do banco para o MVP.
|
||||
</div>
|
||||
|
||||
<pre><code>
|
||||
Estou desenvolvendo um SaaS clínico multi-tenant usando Supabase (Postgres + RLS + Views)
|
||||
com planos e assinaturas.
|
||||
|
||||
══════════════════════════════════════
|
||||
📦 Domínio: Billing / Planos
|
||||
══════════════════════════════════════
|
||||
|
||||
Decisões do MVP:
|
||||
- Tudo começa grátis (clinic e therapist).
|
||||
- Paciente não tem plano (portal do paciente é feature do plano do therapist/clinic).
|
||||
- Plano (billing) NÃO é role (RBAC). Role dirige menus/rotas; plano dirige features/limites.
|
||||
- Planos por target: clinic e therapist.
|
||||
|
||||
Catálogo de planos (MVP):
|
||||
- clinic_free
|
||||
- clinic_pro
|
||||
- therapist_free
|
||||
- therapist_pro
|
||||
|
||||
Views fonte de verdade:
|
||||
- v_public_pricing (tela de preços)
|
||||
- v_plan_active_prices (infra)
|
||||
- v_tenant_active_subscription (gating por tenant)
|
||||
- v_subscription_health (debug)
|
||||
|
||||
Tabelas principais:
|
||||
- plans (colunas reais: key, target, ...)
|
||||
- plan_prices (tem vigência; preço vigente: is_active=true e active_to is null; a UI usa v_plan_active_prices)
|
||||
- plan_public + plan_public_bullets (marketing)
|
||||
- plan_features (entitlements)
|
||||
- subscriptions (+ events, intents)
|
||||
- entitlements_invalidation
|
||||
|
||||
Preços sugeridos (MVP):
|
||||
- clinic_pro: 14900/mês e 149000/ano (BRL)
|
||||
- therapist_pro: 4900/mês e 49000/ano (BRL)
|
||||
- free: grátis (pode manter sem preços)
|
||||
|
||||
Problema já observado:
|
||||
- v_public_pricing retornou null quando plan_prices tinha histórico mas não tinha preço vigente.
|
||||
|
||||
Estado atual (confirmado):
|
||||
- Apenas 4 planos existem (clinic_free/clinic_pro/therapist_free/therapist_pro)
|
||||
|
||||
Objetivo do próximo passo:
|
||||
- Seedar plan_features (entitlements) mapeando features -> feature_id e limits jsonb para nova instalação com os 4 planos + public metadata + preços PRO vigentes.
|
||||
</code></pre>
|
||||
|
||||
<div class="ok">
|
||||
Este prompt deve ser tratado como contexto estrutural completo do Billing no MVP.
|
||||
Qualquer solução proposta deve respeitar essa organização.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="13-tags">
|
||||
<h2>Tags</h2>
|
||||
<span class="pill">#Billing</span>
|
||||
<span class="pill">#Planos</span>
|
||||
<span class="pill">#Pricing</span>
|
||||
<span class="pill">#Seeder</span>
|
||||
<span class="pill">#Supabase</span>
|
||||
<span class="pill">#Postgres</span>
|
||||
<span class="pill">#MultiTenant</span>
|
||||
<span class="pill">#Entitlements</span>
|
||||
<span class="pill">#Subscriptions</span>
|
||||
<span class="pill">#v_public_pricing</span>
|
||||
<span class="pill">#MVP</span>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
Agência PSI • Documentação interna • Billing (Planos/Assinaturas/Seeder)
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
BIN
docs/specs/spec-v2.docx
Normal file
BIN
docs/specs/spec-v2.docx
Normal file
Binary file not shown.
BIN
docs/specs/spec-wizard.docx
Normal file
BIN
docs/specs/spec-wizard.docx
Normal file
Binary file not shown.
@@ -0,0 +1,612 @@
|
||||
<!doctype html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Relatório Técnico — Sessão de Correção: Subscription Health & Entitlements (2026-03-01) | Agência PSI</title>
|
||||
<style>
|
||||
:root{
|
||||
--bg0:#f6f8fc;
|
||||
--bg1:#eef2f8;
|
||||
--panel:rgba(255,255,255,.78);
|
||||
--panel2:rgba(255,255,255,.92);
|
||||
--border:rgba(15,23,42,.10);
|
||||
--text:rgba(15,23,42,.92);
|
||||
--muted:rgba(15,23,42,.70);
|
||||
--muted2:rgba(15,23,42,.56);
|
||||
--accent:#2563eb;
|
||||
--accent2:#4f46e5;
|
||||
--warn:#b45309;
|
||||
--danger:#b91c1c;
|
||||
--ok:#047857;
|
||||
--shadow: 0 18px 60px rgba(2,6,23,.10);
|
||||
--radius: 16px;
|
||||
--radius2: 22px;
|
||||
--mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
||||
}
|
||||
*{box-sizing:border-box;}
|
||||
body{
|
||||
margin:0;
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial;
|
||||
background: radial-gradient(1200px 600px at 10% 0%, rgba(79,70,229,.10), transparent 55%),
|
||||
radial-gradient(1100px 500px at 90% 10%, rgba(37,99,235,.10), transparent 55%),
|
||||
linear-gradient(180deg, var(--bg0), var(--bg1));
|
||||
color:var(--text);
|
||||
}
|
||||
a{color:inherit; text-decoration:none;}
|
||||
a:hover{text-decoration:underline;}
|
||||
.layout{
|
||||
display:grid;
|
||||
grid-template-columns: 320px 1fr;
|
||||
gap: 20px;
|
||||
max-width: 1320px;
|
||||
margin: 0 auto;
|
||||
padding: 28px 18px 42px;
|
||||
}
|
||||
header{
|
||||
grid-column: 1 / -1;
|
||||
padding: 18px;
|
||||
border: 1px solid var(--border);
|
||||
background: linear-gradient(180deg, rgba(255,255,255,.92), rgba(255,255,255,.72));
|
||||
border-radius: var(--radius2);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.kicker{
|
||||
font-size:12px;
|
||||
letter-spacing:.08em;
|
||||
text-transform:uppercase;
|
||||
color:var(--muted2);
|
||||
margin:0 0 8px;
|
||||
}
|
||||
h1{
|
||||
margin:0 0 8px;
|
||||
font-size:30px;
|
||||
letter-spacing:-0.02em;
|
||||
}
|
||||
.subtitle{
|
||||
margin:0;
|
||||
color:var(--muted);
|
||||
max-width:980px;
|
||||
line-height:1.55;
|
||||
font-size:14px;
|
||||
}
|
||||
.meta-row{
|
||||
margin-top:12px;
|
||||
display:flex;
|
||||
flex-wrap:wrap;
|
||||
gap:10px;
|
||||
align-items:center;
|
||||
}
|
||||
.pill{
|
||||
display:inline-flex;
|
||||
align-items:center;
|
||||
gap:8px;
|
||||
padding:8px 12px;
|
||||
border-radius:999px;
|
||||
border:1px solid var(--border);
|
||||
background:rgba(255,255,255,.72);
|
||||
color:var(--muted);
|
||||
font-size:12px;
|
||||
}
|
||||
.dot{
|
||||
width:8px;height:8px;border-radius:50%;
|
||||
background:var(--accent);
|
||||
box-shadow:0 0 0 4px rgba(37,99,235,.12);
|
||||
}
|
||||
aside{
|
||||
position:sticky;
|
||||
top:18px;
|
||||
align-self:start;
|
||||
border:1px solid var(--border);
|
||||
background:var(--panel2);
|
||||
border-radius:var(--radius);
|
||||
box-shadow:var(--shadow);
|
||||
overflow:hidden;
|
||||
}
|
||||
.toc-head{
|
||||
padding:14px;
|
||||
border-bottom:1px solid var(--border);
|
||||
background:rgba(15,23,42,.02);
|
||||
}
|
||||
.toc-title{ margin:0 0 6px; font-weight:700; font-size:14px; }
|
||||
.toc-sub{ margin:0; color:var(--muted); font-size:12px; line-height:1.45; }
|
||||
nav{ padding: 10px 6px 14px; }
|
||||
nav a{
|
||||
display:block;
|
||||
padding:10px 12px;
|
||||
margin:4px 6px;
|
||||
border-radius:12px;
|
||||
color:var(--muted);
|
||||
font-size:13px;
|
||||
border:1px solid transparent;
|
||||
}
|
||||
nav a:hover{
|
||||
background:rgba(37,99,235,.06);
|
||||
border-color:rgba(37,99,235,.12);
|
||||
color:var(--text);
|
||||
text-decoration:none;
|
||||
}
|
||||
.nav-sec{
|
||||
margin:10px 12px 6px;
|
||||
color:var(--muted2);
|
||||
font-size:11px;
|
||||
letter-spacing:.08em;
|
||||
text-transform:uppercase;
|
||||
}
|
||||
main{
|
||||
border:1px solid var(--border);
|
||||
background:var(--panel);
|
||||
border-radius:var(--radius2);
|
||||
box-shadow:var(--shadow);
|
||||
overflow:hidden;
|
||||
}
|
||||
.content{ padding: 18px 18px 22px; }
|
||||
.section{
|
||||
padding: 18px;
|
||||
border:1px solid var(--border);
|
||||
border-radius: var(--radius2);
|
||||
background: rgba(255,255,255,.80);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.section h2{
|
||||
margin:0 0 10px;
|
||||
font-size:18px;
|
||||
letter-spacing:-0.01em;
|
||||
}
|
||||
.section h3{
|
||||
margin:14px 0 8px;
|
||||
font-size:14px;
|
||||
}
|
||||
.section p, .section li{
|
||||
color:var(--muted);
|
||||
line-height:1.65;
|
||||
font-size:13.5px;
|
||||
}
|
||||
.grid{
|
||||
display:grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
@media (max-width: 980px){
|
||||
.layout{ grid-template-columns: 1fr; }
|
||||
aside{ position:relative; top:auto; }
|
||||
.grid{ grid-template-columns: 1fr; }
|
||||
}
|
||||
.callout{
|
||||
border-radius: 16px;
|
||||
padding: 12px 12px 12px 14px;
|
||||
border:1px solid var(--border);
|
||||
background: rgba(15,23,42,.02);
|
||||
margin-top: 10px;
|
||||
}
|
||||
.callout strong{color:var(--text);}
|
||||
.callout.ok{ border-left: 4px solid var(--ok); background: rgba(4,120,87,.06); }
|
||||
.callout.warn{ border-left: 4px solid var(--warn); background: rgba(180,83,9,.08); }
|
||||
.callout.danger{ border-left: 4px solid var(--danger); background: rgba(185,28,28,.08); }
|
||||
.callout.info{ border-left: 4px solid var(--accent); background: rgba(37,99,235,.08); }
|
||||
pre{
|
||||
margin: 10px 0 0;
|
||||
padding: 14px;
|
||||
background: #0b1220;
|
||||
color:#e2e8f0;
|
||||
border-radius: 16px;
|
||||
overflow:auto;
|
||||
border: 1px solid rgba(226,232,240,.08);
|
||||
}
|
||||
code{ font-family: var(--mono); font-size:12.5px; }
|
||||
.kbd{
|
||||
font-family: var(--mono);
|
||||
font-size: 12px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border);
|
||||
background: rgba(255,255,255,.65);
|
||||
color: var(--text);
|
||||
}
|
||||
.hr{
|
||||
height:1px;
|
||||
background: var(--border);
|
||||
margin: 12px 0;
|
||||
}
|
||||
footer{
|
||||
margin-top: 12px;
|
||||
padding: 14px 18px 18px;
|
||||
color: var(--muted2);
|
||||
font-size: 12px;
|
||||
border-top: 1px solid var(--border);
|
||||
background: rgba(255,255,255,.70);
|
||||
}
|
||||
.small{ font-size:12px; color:var(--muted2); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="layout">
|
||||
<header>
|
||||
<p class="kicker">Relatório Técnico • Billing Health • Agência PSI</p>
|
||||
<h1>Subscription Health & Entitlements — Sessão de Correção (2026-03-01)</h1>
|
||||
<p class="subtitle">Este documento registra, de forma minuciosa e operacional, a sessão de diagnóstico e correção dos problemas
|
||||
na página <strong>Saúde das Assinaturas</strong> (Subscription Health) e no pipeline de <strong>Entitlements</strong>.
|
||||
O objetivo é permitir que qualquer programador entenda o incidente, replique o diagnóstico e aplique correções
|
||||
com segurança, mesmo sem ter acompanhado a conversa original.</p>
|
||||
<div class="meta-row">
|
||||
<span class="pill"><span class="dot"></span><strong>Estado:</strong> resolvido e hardenizado</span>
|
||||
<span class="pill"><strong>Atualizado:</strong> 2026-03-01 11:46:44 UTC</span>
|
||||
<span class="pill"><strong>Stack:</strong> Supabase + Postgres + Vue/PrimeVue</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<aside>
|
||||
<div class="toc-head">
|
||||
<div class="toc-title">Sumário</div>
|
||||
<p class="toc-sub">Leitura rápida com passos reproduzíveis (SQL + patches + checklist).</p>
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<div class="nav-sec">Visão geral</div>
|
||||
<a href="#01">Resumo executivo</a>
|
||||
<a href="#02">Escopo e componentes</a>
|
||||
<a href="#03">Sintomas e evidências</a>
|
||||
<div class="nav-sec">Diagnóstico</div>
|
||||
<a href="#04">Causa raiz</a>
|
||||
<a href="#05">SQLs de diagnóstico</a>
|
||||
<div class="nav-sec">Correções</div>
|
||||
<a href="#06">Patches aplicados</a>
|
||||
<a href="#07">Hardening</a>
|
||||
<div class="nav-sec">Validação</div>
|
||||
<a href="#08">Checklist pós-correção</a>
|
||||
<div class="nav-sec">Contexto</div>
|
||||
<a href="#09">Notas de front-end</a>
|
||||
<a href="#10">Linha do tempo</a>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<main>
|
||||
<div class="content">
|
||||
|
||||
<section class="section" id="01">
|
||||
<h2>1. Resumo executivo</h2>
|
||||
|
||||
<p>
|
||||
<strong>Sintoma principal:</strong> a tela <em>Saúde das Assinaturas</em> exibia divergências e a coluna <strong>Owner</strong> aparecia vazia
|
||||
(linhas com <code>owner_id = NULL</code>). Além disso, os botões <strong>Fix</strong> e <strong>Fix All</strong> falhavam.
|
||||
</p>
|
||||
<div class="callout danger">
|
||||
<strong>Impacto:</strong> a ferramenta de diagnóstico do Billing ficou pouco confiável e os reparos automáticos não executavam, impedindo
|
||||
correções rápidas após mudanças de plano/feature.
|
||||
</div>
|
||||
<div class="callout ok">
|
||||
<strong>Resultado final:</strong> view de entitlements corrigida (filtra <code>subscriptions.status = 'active'</code> e exclui NULL),
|
||||
funções RPC alinhadas ao schema atual (<code>subscriptions.user_id</code>), dados inválidos removidos e constraints/índices adicionados
|
||||
para impedir regressões.
|
||||
</div>
|
||||
|
||||
</section>
|
||||
<section class="section" id="02">
|
||||
<h2>2. Escopo e componentes envolvidos</h2>
|
||||
|
||||
<div class="grid">
|
||||
<div class="callout">
|
||||
<strong>View de saúde</strong>
|
||||
<p><code>public.v_subscription_feature_mismatch</code> — compara o esperado (plan_features do plano ativo) com o atual (entitlements).</p>
|
||||
</div>
|
||||
<div class="callout">
|
||||
<strong>Entitlements agregados</strong>
|
||||
<p><code>public.owner_feature_entitlements</code> — <em>VIEW</em> agregada (sources + limits_list), derivada de <code>subscriptions</code> e <code>tenant_modules</code>.</p>
|
||||
</div>
|
||||
<div class="callout">
|
||||
<strong>Rotinas de reparo</strong>
|
||||
<p><code>public.rebuild_owner_entitlements(uuid)</code> e <code>public.fix_all_subscription_mismatches()</code>.</p>
|
||||
</div>
|
||||
<div class="callout">
|
||||
<strong>Tabelas de configuração</strong>
|
||||
<p><code>plans</code>, <code>features</code>, <code>plan_features</code>, <code>module_features</code>, <code>tenant_modules</code>, <code>subscriptions</code>.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
<section class="section" id="03">
|
||||
<h2>3. Sintomas observados e evidências</h2>
|
||||
|
||||
<p>Foram observados os seguintes indícios no banco:</p>
|
||||
<ul>
|
||||
<li>View <code>v_subscription_feature_mismatch</code> retornando <code>owner_id = NULL</code> tanto em <em>missing</em> quanto em <em>unexpected</em>.</li>
|
||||
<li>Contagem estável em 4/4 divergências, mesmo após tentativa de reparo.</li>
|
||||
<li>Existência de uma <code>subscription</code> com <code>status='active'</code> e <code>user_id = NULL</code> (dado inválido).</li>
|
||||
<li>Falha de execução do FixAll com erro de coluna inexistente: <code>subscriptions.owner_id</code> (schema drift).</li>
|
||||
</ul>
|
||||
|
||||
<div class="callout warn">
|
||||
<strong>Nota de leitura:</strong> ao ver <code>owner_id = NULL</code> em divergências, trate como anomalia de dados ou view/joins permissivos.
|
||||
Na prática, “owner nulo” não é um caso de negócio — é um caso de <em>integridade</em>.
|
||||
</div>
|
||||
|
||||
</section>
|
||||
<section class="section" id="04">
|
||||
<h2>4. Diagnóstico e causa raiz</h2>
|
||||
|
||||
<h3>4.1 Causa raiz #1 — Schema drift nas funções RPC</h3>
|
||||
<p>
|
||||
As funções de reparo estavam escritas para um schema anterior, usando <code>subscriptions.owner_id</code>. No schema atual, o owner do contexto
|
||||
“terapeuta” é <code>subscriptions.user_id</code>. Isso quebrou:
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>Fix owner</strong>: falha ao buscar o plano ativo do owner.</li>
|
||||
<li><strong>Fix all</strong>: falha ao iterar owners e chamar o rebuild.</li>
|
||||
</ul>
|
||||
|
||||
<div class="callout danger">
|
||||
<strong>Erro observado:</strong> <code>column "owner_id" does not exist</code> (hint citando <code>subscriptions.user_id</code>).
|
||||
</div>
|
||||
|
||||
<h3>4.2 Causa raiz #2 — View de entitlements agregados não filtrava status</h3>
|
||||
<p>
|
||||
A view <code>owner_feature_entitlements</code> agregava a fonte “plan” sem filtrar <code>subscriptions.status</code>, permitindo que uma subscription
|
||||
<em>inactive</em> com <code>user_id NULL</code> continuasse “vazando” entitlements com <code>owner_id NULL</code> para o sistema.
|
||||
</p>
|
||||
|
||||
<h3>4.3 Causa raiz #3 — Dado inválido</h3>
|
||||
<p>
|
||||
Foi identificado um registro em <code>subscriptions</code> com <code>user_id NULL</code>. Mesmo após torná-lo <em>inactive</em>, ele continuava contaminando
|
||||
a view (por ausência do filtro de status).
|
||||
</p>
|
||||
|
||||
</section>
|
||||
<section class="section" id="05">
|
||||
<h2>5. SQLs usados no diagnóstico (playbook)</h2>
|
||||
|
||||
<p>Use este bloco para reproduzir o diagnóstico com segurança.</p>
|
||||
|
||||
<h3>5.1 Ver divergências e amostras</h3>
|
||||
<pre><code>select mismatch_type, count(*) as qtd
|
||||
from public.v_subscription_feature_mismatch
|
||||
group by 1
|
||||
order by 2 desc;
|
||||
|
||||
select owner_id, feature_key, mismatch_type
|
||||
from public.v_subscription_feature_mismatch
|
||||
order by owner_id nulls first, feature_key
|
||||
limit 50;</code></pre>
|
||||
|
||||
<h3>5.2 Encontrar subscriptions inválidas (user_id nulo)</h3>
|
||||
<pre><code>select id, user_id, plan_id, status, created_at
|
||||
from public.subscriptions
|
||||
where user_id is null
|
||||
order by created_at desc;</code></pre>
|
||||
|
||||
<h3>5.3 Entender a origem dos entitlements agregados</h3>
|
||||
<pre><code>select pg_get_viewdef('public.owner_feature_entitlements'::regclass, true) as view_sql;</code></pre>
|
||||
|
||||
<h3>5.4 Verificar tenant_modules inválidos (owner_id nulo)</h3>
|
||||
<pre><code>select count(*) as qtd
|
||||
from public.tenant_modules
|
||||
where status = 'active' and owner_id is null;</code></pre>
|
||||
|
||||
</section>
|
||||
<section class="section" id="06">
|
||||
<h2>6. Correções aplicadas no banco (patches)</h2>
|
||||
|
||||
<h3>6.1 Patch: rebuild_owner_entitlements (owner = subscriptions.user_id)</h3>
|
||||
<p>
|
||||
Ajuste para buscar o plano ativo por <code>subscriptions.user_id</code> e reconstruir entitlements com base em <code>plan_features</code>.
|
||||
</p>
|
||||
<pre><code>create or replace function public.rebuild_owner_entitlements(p_owner_id uuid)
|
||||
returns void
|
||||
language plpgsql
|
||||
security definer
|
||||
as $$
|
||||
declare
|
||||
v_plan_id uuid;
|
||||
begin
|
||||
select s.plan_id
|
||||
into v_plan_id
|
||||
from public.subscriptions s
|
||||
where s.user_id = p_owner_id
|
||||
and s.status = 'active'
|
||||
order by s.created_at desc
|
||||
limit 1;
|
||||
|
||||
delete from public.owner_feature_entitlements e
|
||||
where e.owner_id = p_owner_id;
|
||||
|
||||
if v_plan_id is null then
|
||||
return;
|
||||
end if;
|
||||
|
||||
insert into public.owner_feature_entitlements (owner_id, feature_key, sources, limits_list)
|
||||
select
|
||||
p_owner_id,
|
||||
f.key,
|
||||
array['plan'::text],
|
||||
'{}'::jsonb
|
||||
from public.plan_features pf
|
||||
join public.features f on f.id = pf.feature_id
|
||||
where pf.plan_id = v_plan_id;
|
||||
end;
|
||||
$$;</code></pre>
|
||||
|
||||
<div class="callout warn">
|
||||
<strong>Importante:</strong> se <code>owner_feature_entitlements</code> for uma <em>VIEW</em> (como no ambiente desta sessão),
|
||||
o <code>DELETE/INSERT</code> acima deve ser direcionado à <em>tabela base</em> real de entitlements, se existir.
|
||||
Nesta sessão, a correção definitiva foi feita ajustando a view agregadora e limpando o dado inválido.
|
||||
</div>
|
||||
|
||||
<h3>6.2 Patch: fix_all_subscription_mismatches (itera subscriptions.user_id)</h3>
|
||||
<pre><code>create or replace function public.fix_all_subscription_mismatches()
|
||||
returns void
|
||||
language plpgsql
|
||||
security definer
|
||||
as $$
|
||||
declare
|
||||
r record;
|
||||
begin
|
||||
for r in
|
||||
select distinct s.user_id as owner_id
|
||||
from public.subscriptions s
|
||||
where s.status = 'active'
|
||||
and s.user_id is not null
|
||||
loop
|
||||
perform public.rebuild_owner_entitlements(r.owner_id);
|
||||
end loop;
|
||||
end;
|
||||
$$;</code></pre>
|
||||
|
||||
<h3>6.3 Patch: owner_feature_entitlements (filtra status e NULLs)</h3>
|
||||
<pre><code>create or replace view public.owner_feature_entitlements as
|
||||
with base as (
|
||||
select
|
||||
s.user_id as owner_id,
|
||||
f.key as feature_key,
|
||||
pf.limits,
|
||||
'plan'::text as source
|
||||
from public.subscriptions s
|
||||
join public.plan_features pf
|
||||
on pf.plan_id = s.plan_id
|
||||
and pf.enabled = true
|
||||
join public.features f
|
||||
on f.id = pf.feature_id
|
||||
where s.status = 'active'
|
||||
and s.user_id is not null
|
||||
|
||||
union all
|
||||
|
||||
select
|
||||
tm.owner_id,
|
||||
f.key as feature_key,
|
||||
mf.limits,
|
||||
'module'::text as source
|
||||
from public.tenant_modules tm
|
||||
join public.modules m
|
||||
on m.id = tm.module_id
|
||||
and m.is_active = true
|
||||
join public.module_features mf
|
||||
on mf.module_id = m.id
|
||||
and mf.enabled = true
|
||||
join public.features f
|
||||
on f.id = mf.feature_id
|
||||
where tm.status = 'active'
|
||||
and tm.owner_id is not null
|
||||
)
|
||||
select
|
||||
owner_id,
|
||||
feature_key,
|
||||
array_agg(distinct source) as sources,
|
||||
jsonb_agg(limits) filter (where limits is not null) as limits_list
|
||||
from base
|
||||
group by owner_id, feature_key;</code></pre>
|
||||
|
||||
<h3>6.4 Limpeza do dado inválido (subscription com user_id NULL)</h3>
|
||||
<pre><code>-- se for lixo de seed/teste (recomendado remover):
|
||||
delete from public.subscriptions
|
||||
where user_id is null;</code></pre>
|
||||
|
||||
</section>
|
||||
<section class="section" id="07">
|
||||
<h2>7. Hardening (constraints e índices recomendados)</h2>
|
||||
|
||||
<p>Após corrigir dados e views, aplique hardening para impedir regressões.</p>
|
||||
|
||||
<h3>7.1 subscriptions.user_id NOT NULL</h3>
|
||||
<pre><code>alter table public.subscriptions
|
||||
alter column user_id set not null;</code></pre>
|
||||
|
||||
<h3>7.2 Uma assinatura ativa por usuário</h3>
|
||||
<pre><code>create unique index if not exists subscriptions_one_active_per_user
|
||||
on public.subscriptions (user_id)
|
||||
where status = 'active';</code></pre>
|
||||
|
||||
<h3>7.3 Índice de performance para consultas por owner/status</h3>
|
||||
<pre><code>create index if not exists subscriptions_user_status_idx
|
||||
on public.subscriptions (user_id, status, created_at desc);</code></pre>
|
||||
|
||||
<h3>7.4 tenant_modules.owner_id NOT NULL (decisão tomada nesta sessão)</h3>
|
||||
<pre><code>alter table public.tenant_modules
|
||||
alter column owner_id set not null;</code></pre>
|
||||
|
||||
<h3>7.5 Uniqueness e performance em plan_features / module_features</h3>
|
||||
<pre><code>create unique index if not exists plan_features_plan_feature_ux
|
||||
on public.plan_features (plan_id, feature_id);
|
||||
|
||||
create index if not exists plan_features_plan_enabled_idx
|
||||
on public.plan_features (plan_id, enabled);
|
||||
|
||||
create unique index if not exists module_features_module_feature_ux
|
||||
on public.module_features (module_id, feature_id);</code></pre>
|
||||
|
||||
<div class="callout info">
|
||||
<strong>Regra prática:</strong> dados inválidos (NULL em owner) devem ser bloqueados na borda (constraints), não “corrigidos” no front.
|
||||
</div>
|
||||
|
||||
</section>
|
||||
<section class="section" id="08">
|
||||
<h2>8. Verificação pós-correção (checklist)</h2>
|
||||
|
||||
<h3>8.1 Saúde deve zerar</h3>
|
||||
<pre><code>select mismatch_type, count(*) as qtd
|
||||
from public.v_subscription_feature_mismatch
|
||||
group by 1
|
||||
order by 2 desc;</code></pre>
|
||||
|
||||
<h3>8.2 Não pode haver owner nulo em subscriptions / tenant_modules ativos</h3>
|
||||
<pre><code>select count(*) as subs_user_null
|
||||
from public.subscriptions
|
||||
where user_id is null;
|
||||
|
||||
select count(*) as tenant_modules_active_owner_null
|
||||
from public.tenant_modules
|
||||
where status='active' and owner_id is null;</code></pre>
|
||||
|
||||
<h3>8.3 Entitlements agregados não devem conter owner null</h3>
|
||||
<pre><code>select owner_id, feature_key
|
||||
from public.owner_feature_entitlements
|
||||
where owner_id is null
|
||||
limit 20;</code></pre>
|
||||
|
||||
<div class="callout ok">
|
||||
<strong>OK final:</strong> todas as queries acima retornam 0 linhas (ou contagens zero).
|
||||
</div>
|
||||
|
||||
</section>
|
||||
<section class="section" id="09">
|
||||
<h2>9. Notas de implementação no front-end (contexto)</h2>
|
||||
|
||||
<p>Durante a sessão, a UI foi ajustada para:</p>
|
||||
<ul>
|
||||
<li>Traduzir telas para PT-BR, melhorar títulos, descrições e mensagens.</li>
|
||||
<li>Padronizar inputs com <code>FloatLabel</code> + <code>IconField</code> + <code>InputIcon</code>.</li>
|
||||
<li>Adicionar confirmações e “alterações pendentes” em ações em massa (plan_features), evitando salvar por clique acidental.</li>
|
||||
<li>Garantir que ações de correção (Fix/FixAll) reflitam erros reais (RPC quebrada vs dados inválidos).</li>
|
||||
</ul>
|
||||
|
||||
<div class="callout warn">
|
||||
<strong>Regra operacional:</strong> se a coluna Owner aparecer vazia, corrija no banco primeiro (dados/view),
|
||||
antes de mexer no front.
|
||||
</div>
|
||||
|
||||
</section>
|
||||
<section class="section" id="10">
|
||||
<h2>10. Linha do tempo da sessão (resumo)</h2>
|
||||
|
||||
<ul>
|
||||
<li><strong>Detecção:</strong> tela “Saúde das Assinaturas” exibindo Owner vazio e divergências.</li>
|
||||
<li><strong>Inspeção:</strong> <code>v_subscription_feature_mismatch</code> mostrava <code>owner_id NULL</code> em missing/unexpected.</li>
|
||||
<li><strong>Erro crítico:</strong> FixAll falhava com <code>subscriptions.owner_id</code> inexistente.</li>
|
||||
<li><strong>Correção #1:</strong> alinhar RPCs ao schema atual (<code>subscriptions.user_id</code>).</li>
|
||||
<li><strong>Correção #2:</strong> identificar que <code>owner_feature_entitlements</code> é VIEW e filtrar <code>status='active'</code>.</li>
|
||||
<li><strong>Correção #3:</strong> remover subscription inválida com <code>user_id NULL</code>.</li>
|
||||
<li><strong>Hardening:</strong> constraints e índices para prevenir regressões.</li>
|
||||
</ul>
|
||||
|
||||
<div class="callout info">
|
||||
<strong>Atualizado:</strong> 2026-03-01 11:46:44 UTC
|
||||
</div>
|
||||
|
||||
</section>
|
||||
</div>
|
||||
<footer>
|
||||
<div><strong>Agência PSI — Relatório Técnico (Billing Health)</strong></div>
|
||||
<div class="small">Documento operacional inspirado no “Documento Mestre Billing v2.0”. Atualizado em 2026-03-01 11:46:44 UTC.</div>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
243
docs/whatsapp.md
Normal file
243
docs/whatsapp.md
Normal file
@@ -0,0 +1,243 @@
|
||||
# 📱 Disparando WhatsApp Local — AgênciaPsi
|
||||
|
||||
Guia completo para rodar o sistema de lembretes por WhatsApp no ambiente local de desenvolvimento.
|
||||
|
||||
---
|
||||
|
||||
## 🧱 O que você precisa ter rodando
|
||||
|
||||
| Serviço | Como subir | Porta |
|
||||
|---|---|---|
|
||||
| Supabase local | `npx supabase start` | 54321 |
|
||||
| Evolution API | Docker Desktop | 8080 |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Passo 1 — Subir o Supabase local
|
||||
|
||||
Abre o PowerShell na pasta do projeto e roda:
|
||||
|
||||
```powershell
|
||||
cd D:\leonohama\AgenciaPsi.com.br\Sistema\agenciapsi-primesakai
|
||||
npx supabase start
|
||||
```
|
||||
|
||||
Aguarda aparecer as URLs e credenciais. Confirma que está rodando acessando:
|
||||
```
|
||||
http://127.0.0.1:54323
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐳 Passo 2 — Subir a Evolution API
|
||||
|
||||
Abre o Docker Desktop e confirma que está com o status **Engine running**.
|
||||
|
||||
Abre outro PowerShell e roda:
|
||||
|
||||
```powershell
|
||||
cd D:\leonohama\AgenciaPsi.com.br\Sistema\agenciapsi-primesakai\evolution-api
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Confirma que está rodando acessando:
|
||||
```
|
||||
http://localhost:8080
|
||||
```
|
||||
|
||||
Deve retornar:
|
||||
```json
|
||||
{"status":200,"message":"Welcome to the Evolution API, it is working!"}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📱 Passo 3 — Conectar o WhatsApp
|
||||
|
||||
Acessa o painel da Evolution API:
|
||||
```
|
||||
http://localhost:8080/manager
|
||||
```
|
||||
|
||||
- Loga com a API key: `minha_chave_123`
|
||||
- Verifica se a instância `agenciapsi-teste` está com status **Connected**
|
||||
- Se estiver **Disconnected**, clica na instância → **Get QR Code** → escaneia com o celular
|
||||
|
||||
**Para escanear no celular:**
|
||||
WhatsApp → ⋮ (três pontinhos) → Aparelhos conectados → Conectar um aparelho
|
||||
|
||||
---
|
||||
|
||||
## ⚡ Passo 4 — Subir a Edge Function
|
||||
|
||||
Abre outro PowerShell e roda:
|
||||
|
||||
```powershell
|
||||
cd D:\leonohama\AgenciaPsi.com.br\Sistema\agenciapsi-primesakai
|
||||
npx supabase functions serve process-notification-queue --no-verify-jwt
|
||||
```
|
||||
|
||||
Aguarda aparecer:
|
||||
```
|
||||
- http://127.0.0.1:54321/functions/v1/process-notification-queue
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🗄️ Passo 5 — Popular a fila manualmente
|
||||
|
||||
Acessa o Supabase Studio em `http://127.0.0.1:54323`, vai em **SQL Editor** e roda:
|
||||
|
||||
```sql
|
||||
-- Popula a fila com sessões das próximas 48h
|
||||
select populate_notification_queue();
|
||||
|
||||
-- Verifica o que foi gerado
|
||||
select id, status, scheduled_at, channel, recipient_address, resolved_vars
|
||||
from notification_queue
|
||||
order by created_at desc
|
||||
limit 10;
|
||||
```
|
||||
|
||||
Se precisar corrigir o número do destinatário:
|
||||
|
||||
```sql
|
||||
update notification_queue
|
||||
set recipient_address = '5516999999999' -- coloca o número real aqui
|
||||
where status = 'pendente';
|
||||
```
|
||||
|
||||
Se precisar reenviar um item que falhou:
|
||||
|
||||
```sql
|
||||
update notification_queue
|
||||
set status = 'pendente'
|
||||
where id = 'cole-o-uuid-aqui';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📤 Passo 6 — Disparar a fila
|
||||
|
||||
Abre outro PowerShell e roda:
|
||||
|
||||
```powershell
|
||||
Invoke-WebRequest `
|
||||
-Uri "http://127.0.0.1:54321/functions/v1/process-notification-queue" `
|
||||
-Method POST `
|
||||
-Headers @{"Authorization"="Bearer sb_secret_N7UND0UgjKTVK-Uodkm0Hg_xSvEMPvz"} `
|
||||
-TimeoutSec 30 `
|
||||
| Select-Object -ExpandProperty Content
|
||||
```
|
||||
|
||||
**Resposta esperada:**
|
||||
```json
|
||||
{"processados":[{"id":"...","status":"enviado"}]}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Passo 7 — Simular o cron (disparo automático a cada 5 min)
|
||||
|
||||
Para simular o `pg_cron` localmente, cria um arquivo `cron-local.ps1` na raiz do projeto:
|
||||
|
||||
```powershell
|
||||
while ($true) {
|
||||
Write-Host "$(Get-Date) - Populando fila..."
|
||||
Invoke-WebRequest `
|
||||
-Uri "http://127.0.0.1:54321/functions/v1/process-notification-queue" `
|
||||
-Method POST `
|
||||
-Headers @{"Authorization"="Bearer sb_secret_N7UND0UgjKTVK-Uodkm0Hg_xSvEMPvz"} `
|
||||
-TimeoutSec 30 `
|
||||
| Select-Object -ExpandProperty Content
|
||||
Write-Host "$(Get-Date) - Aguardando 5 minutos..."
|
||||
Start-Sleep -Seconds 300
|
||||
}
|
||||
```
|
||||
|
||||
Roda com:
|
||||
```powershell
|
||||
cd D:\leonohama\AgenciaPsi.com.br\Sistema\agenciapsi-primesakai
|
||||
.\cron-local.ps1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Passo 8 — Verificar logs
|
||||
|
||||
No Supabase Studio, roda:
|
||||
|
||||
```sql
|
||||
-- Ver histórico de envios
|
||||
select id, status, channel, recipient_address, sent_at, failure_reason
|
||||
from notification_logs
|
||||
order by created_at desc
|
||||
limit 10;
|
||||
|
||||
-- Ver status da fila
|
||||
select status, count(*)
|
||||
from notification_queue
|
||||
group by status;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Teste rápido de envio direto (sem fila)
|
||||
|
||||
Para testar se o WhatsApp está funcionando sem passar pela fila:
|
||||
|
||||
```powershell
|
||||
$body = '{"number":"5516999999999","text":"Teste direto AgênciaPsi!"}'
|
||||
$headers = @{"apikey"="minha_chave_123"; "Content-Type"="application/json"}
|
||||
Invoke-WebRequest `
|
||||
-Uri "http://localhost:8080/message/sendText/agenciapsi-teste" `
|
||||
-Method POST `
|
||||
-Headers $headers `
|
||||
-Body $body `
|
||||
-TimeoutSec 30 `
|
||||
| Select-Object -ExpandProperty Content
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Problemas comuns
|
||||
|
||||
| Problema | Causa | Solução |
|
||||
|---|---|---|
|
||||
| `Function not found` | Terminal na pasta errada | `cd` para a raiz do projeto antes de rodar o `functions serve` |
|
||||
| `{"count":0}` no QR Code | Bug da versão | Usar imagem `evoapicloud/evolution-api:latest` |
|
||||
| `Nenhum item na fila` | Item já processado ou com status diferente | Resetar com `update notification_queue set status = 'pendente'` |
|
||||
| Timeout no envio | Redis não está rodando | Verificar se o container `evolution-redis` está up no Docker |
|
||||
| `undefined/message/sendText/undefined` | Campos errados nas credenciais | Verificar se `credentials` tem `api_url` e `instance_name` |
|
||||
|
||||
---
|
||||
|
||||
## 📋 Resumo das credenciais locais
|
||||
|
||||
```
|
||||
Supabase URL: http://127.0.0.1:54321
|
||||
Supabase Studio: http://127.0.0.1:54323
|
||||
Supabase Secret Key: sb_secret_N7UND0UgjKTVK-Uodkm0Hg_xSvEMPvz
|
||||
Evolution API URL: http://localhost:8080
|
||||
Evolution API Key: minha_chave_123
|
||||
Instância WhatsApp: agenciapsi-teste
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quando for para produção
|
||||
|
||||
1. Subir a Evolution API em um VPS (Hostinger, Contabo ~R$30/mês)
|
||||
2. Atualizar `api_url` em `notification_channels` para a URL do VPS
|
||||
3. Configurar o `pg_cron` no Supabase cloud:
|
||||
|
||||
```sql
|
||||
select cron.schedule(
|
||||
'populate-notification-queue',
|
||||
'*/5 * * * *',
|
||||
$$ select populate_notification_queue(); $$
|
||||
);
|
||||
```
|
||||
|
||||
4. Configurar o disparo da Edge Function via `pg_net` ou webhook externo
|
||||
5. Migrar para API Oficial da Meta quando tiver volume de clientes
|
||||
Reference in New Issue
Block a user