From ad2c27d7933f9062a78f2ec191c3ebb0b7a7b3bd Mon Sep 17 00:00:00 2001 From: Pradeep Date: Thu, 13 Nov 2025 09:07:54 +0530 Subject: [PATCH] implemented KG DB --- ...-c95b2815aac1_20251110_032129_analysis.pdf | 1352 +++++++++++++++++ ...-c95b2815aac1_20251110_055924_analysis.pdf | 1251 +++++++++++++++ docker-compose.yml | 55 +- services/ai-analysis-service/__init__.py | 8 + services/ai-analysis-service/ai-analyze.py | 1035 ++++++------- services/ai-analysis-service/env.example | 12 +- .../knowledge_graph/__init__.py | 9 + .../knowledge_graph/neo4j_client.py | 328 ++++ .../knowledge_graph/operations.py | 214 +++ .../ai-analysis-service/progress_manager.py | 10 + services/ai-analysis-service/pyproject.toml | 32 + services/ai-analysis-service/requirements.txt | 1 + services/ai-analysis-service/server.py | 711 ++++----- .../ai-analysis-service/test_data_storage.py | 183 --- .../test_db_connections.py | 106 -- .../test_frontend_compatibility.py | 271 ---- .../test_intelligent_chunking.py | 318 ---- .../test_knowledge_graph_operations.py | 103 ++ .../test_multi_level_report.py | 244 --- .../test_progressive_context.py | 309 ---- 20 files changed, 4080 insertions(+), 2472 deletions(-) create mode 100644 ai-analysis-reports/repo_analysis_e395ea2c-ea3b-43e0-94af-c95b2815aac1_20251110_032129_analysis.pdf create mode 100644 ai-analysis-reports/repo_analysis_e395ea2c-ea3b-43e0-94af-c95b2815aac1_20251110_055924_analysis.pdf create mode 100644 services/ai-analysis-service/__init__.py create mode 100644 services/ai-analysis-service/knowledge_graph/__init__.py create mode 100644 services/ai-analysis-service/knowledge_graph/neo4j_client.py create mode 100644 services/ai-analysis-service/knowledge_graph/operations.py create mode 100644 services/ai-analysis-service/pyproject.toml delete mode 100644 services/ai-analysis-service/test_data_storage.py delete mode 100644 services/ai-analysis-service/test_db_connections.py delete mode 100755 services/ai-analysis-service/test_frontend_compatibility.py delete mode 100644 services/ai-analysis-service/test_intelligent_chunking.py create mode 100644 services/ai-analysis-service/test_knowledge_graph_operations.py delete mode 100755 services/ai-analysis-service/test_multi_level_report.py delete mode 100644 services/ai-analysis-service/test_progressive_context.py diff --git a/ai-analysis-reports/repo_analysis_e395ea2c-ea3b-43e0-94af-c95b2815aac1_20251110_032129_analysis.pdf b/ai-analysis-reports/repo_analysis_e395ea2c-ea3b-43e0-94af-c95b2815aac1_20251110_032129_analysis.pdf new file mode 100644 index 0000000..880c9f2 --- /dev/null +++ b/ai-analysis-reports/repo_analysis_e395ea2c-ea3b-43e0-94af-c95b2815aac1_20251110_032129_analysis.pdf @@ -0,0 +1,1352 @@ +%PDF-1.4 +%“Œ‹ž ReportLab Generated PDF document http://www.reportlab.com +1 0 obj +<< +/F1 2 0 R /F2 3 0 R /F3 5 0 R /F4 7 0 R /F5 17 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 77 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +5 0 obj +<< +/BaseFont /ZapfDingbats /Name /F3 /Subtype /Type1 /Type /Font +>> +endobj +6 0 obj +<< +/Contents 78 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +7 0 obj +<< +/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F4 /Subtype /Type1 /Type /Font +>> +endobj +8 0 obj +<< +/Contents 79 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +9 0 obj +<< +/Contents 80 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +10 0 obj +<< +/Contents 81 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +11 0 obj +<< +/Contents 82 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +12 0 obj +<< +/Contents 83 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +13 0 obj +<< +/Contents 84 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +14 0 obj +<< +/Contents 85 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +15 0 obj +<< +/Contents 86 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +16 0 obj +<< +/Contents 87 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +17 0 obj +<< +/BaseFont /Symbol /Name /F5 /Subtype /Type1 /Type /Font +>> +endobj +18 0 obj +<< +/Contents 88 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +19 0 obj +<< +/Contents 89 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +20 0 obj +<< +/Contents 90 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +21 0 obj +<< +/Contents 91 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +22 0 obj +<< +/Contents 92 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +23 0 obj +<< +/Contents 93 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +24 0 obj +<< +/Contents 94 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +25 0 obj +<< +/Contents 95 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +26 0 obj +<< +/Contents 96 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +27 0 obj +<< +/Contents 97 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +28 0 obj +<< +/Contents 98 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +29 0 obj +<< +/Contents 99 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +30 0 obj +<< +/Contents 100 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +31 0 obj +<< +/Contents 101 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +32 0 obj +<< +/Contents 102 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +33 0 obj +<< +/Contents 103 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +34 0 obj +<< +/Contents 104 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +35 0 obj +<< +/Contents 105 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +36 0 obj +<< +/Contents 106 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +37 0 obj +<< +/Contents 107 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +38 0 obj +<< +/Contents 108 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +39 0 obj +<< +/Contents 109 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +40 0 obj +<< +/Contents 110 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +41 0 obj +<< +/Contents 111 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +42 0 obj +<< +/Contents 112 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +43 0 obj +<< +/Contents 113 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +44 0 obj +<< +/Contents 114 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +45 0 obj +<< +/Contents 115 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +46 0 obj +<< +/Contents 116 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +47 0 obj +<< +/Contents 117 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +48 0 obj +<< +/Contents 118 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +49 0 obj +<< +/Contents 119 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +50 0 obj +<< +/Contents 120 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +51 0 obj +<< +/Contents 121 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +52 0 obj +<< +/Contents 122 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +53 0 obj +<< +/Contents 123 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +54 0 obj +<< +/Contents 124 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +55 0 obj +<< +/Contents 125 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +56 0 obj +<< +/Contents 126 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +57 0 obj +<< +/Contents 127 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +58 0 obj +<< +/Contents 128 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +59 0 obj +<< +/Contents 129 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +60 0 obj +<< +/Contents 130 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +61 0 obj +<< +/Contents 131 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +62 0 obj +<< +/Contents 132 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +63 0 obj +<< +/Contents 133 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +64 0 obj +<< +/Contents 134 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +65 0 obj +<< +/Contents 135 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +66 0 obj +<< +/Contents 136 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +67 0 obj +<< +/Contents 137 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +68 0 obj +<< +/Contents 138 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +69 0 obj +<< +/Contents 139 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +70 0 obj +<< +/Contents 140 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +71 0 obj +<< +/Contents 141 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +72 0 obj +<< +/Contents 142 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +73 0 obj +<< +/Contents 143 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 76 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +74 0 obj +<< +/PageMode /UseNone /Pages 76 0 R /Type /Catalog +>> +endobj +75 0 obj +<< +/Author (\(anonymous\)) /CreationDate (D:20251110032807+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20251110032807+00'00') /Producer (ReportLab PDF Library - www.reportlab.com) + /Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False +>> +endobj +76 0 obj +<< +/Count 67 /Kids [ 4 0 R 6 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 18 0 R 19 0 R 20 0 R 21 0 R 22 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 + 37 0 R 38 0 R 39 0 R 40 0 R 41 0 R 42 0 R 43 0 R 44 0 R 45 0 R 46 0 R + 47 0 R 48 0 R 49 0 R 50 0 R 51 0 R 52 0 R 53 0 R 54 0 R 55 0 R 56 0 R + 57 0 R 58 0 R 59 0 R 60 0 R 61 0 R 62 0 R 63 0 R 64 0 R 65 0 R 66 0 R + 67 0 R 68 0 R 69 0 R 70 0 R 71 0 R 72 0 R 73 0 R ] /Type /Pages +>> +endobj +77 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 610 +>> +stream +GatUogMY_1&;KY!MRfa#.Y7J/gM:3Yg8Bn/4Y0^W)i!QZef20kmn(Jq!#o$B-,MCMjpn."^cQFn\fKQVCi*+!q3s=Ue*s$fA#<@<&G(+OP?Pu'-h_^?BVBZEW=.\DN*`hFM/7`h"""]UV@`Aj!_%P:t$kdf5UuY7G>f5G%N/3.6#^niOo'&4LIJo6l%*e/LMDqNJ??Q-V;t1>rFBlt%dJN^C%Aj:G0n";C2)DFCs0CT?7\Y;bTZ)m*8`"'`4gP>Z<5=qKL2XHT.QRDp;DYm4fqaHP8d%OqBT6Uaerl\2>nWD.;)m.,+C?K,EL0&F766h\#Q<8lqtendstream +endobj +78 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1376 +>> +stream +Gau1.>Ar7c&;B$?.gCH9m357l>qFZINhFlkX=0?uZj$B5-:d&rjK>dnb*QJmZQuYD7_"&h('5b3Uc<""^H"(hcoZW,i]BhjhTtDp%flhcRa!;F]^=)4L5*%0Db!+dA?SeB:A8V?J)841E8t5D_^pi;hdGbmZP9TX;:$t&eTWZ-R19J.lgZ>0/nk%3E:@F^rH1Z[0;HqVjR6$9rBmnu>dQ`bfo=X!DaiFf'Y$D'mnKcDE8dBGiDR;!<'G>s<"\#?dV^-DmPa;%kgG-hZlIfT0RZLe6/K^Og;hqFf"uNUEWnYjZaRrN'ZoaA=8!CY#KWJ<>Q^BN@1a5GWu#$9pChW/;OqhRJHB-26F^&[g"h90/nKe^&c32GS)9?gfMVMMX8\DX_\n!416kYPl1QL;>>K)0?t*cR<(kY\)WuhZ\M:7CW]IhU9-us>)h!dQKB176g^*#^\_@PU5FGucG&]=/aG?/1L+t2(0@TtaA$_IEB=Fd\QWm?QJF46nD^a,hJ0_CdJ;3"IPUQaH&h$R'jp[&S5ZWY$ae2Sn2Kg'peXhqSDHN7s/t]:A1GT1r:#UG1<0req7bXH_LTPXLS\D3?WhP)a:IP>qHeOt$:-F6#K,OU.O0ioPM9M9R(U-Xj^g>W82E(4c:D@QQ./c<2V6^9V/,K6:.IP5@L%qAf',G@bOMHh!VC6r>kY?e(MGOMAX$h_9NdNV:41tNi1,gcf6J4$$L"1FsfretjI5#_+'P]!,2KN_5TgHagUp;I!YP.[@,N/P@:SAmC`ar(b@=W)Nq-oIG5U)kZT,!81`B#r,WW+'Jc[A6^\IhSD3NT#o)C;43*+),@?kt7PrW<&<`]gB(oCG!e>$J5DNem/qjFfmF.DlgWrUlUVWu6JU'9dHU'm,DF32/^_o4,<&!#I@He)a=`JKGWhm@P(rboCb3fSrg+oWd[&,GE&Me9QbM]$kui1eouh"3$8qgf:iL.pd);\hD>_ef?&s2o[rk.==-+Ei*gh?jP'5M.i])$IGpWr/-)PRXt`#M'^2'_7WicEj^CGR"Ap(NMM^dOGRL[nl:7!ZeDGSoZE/bq[=ugL/.Wt1J5'lnOu-1?#kiHQf1O&NSP#&@>5m)2hKAWiJ0&\"[.q%^;+;)m%#.8pgJm:(ZJ30f/S9'Kp$ND[kk\!PGqM]Z\)C.Z8t>a$4&G)M=M%2FS%4gPt6l~>endstream +endobj +79 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1075 +>> +stream +Gatm:?#SFN'Rf.G>cF1C9e),i^>N_4V->7Wd5K`c.TL2'!O%>ID24/MqV?f)bsoQ,!"#@OnNFqUS80o7kf\k@0EF0)iUi961Vk.8Ze?2i6Q#U#k8#R\\fSA)\*\HHO5=R+r%Va9l_3L-;%1N;Mj@r?8ur,DZ7NnN11jkRpajrV9jl$Gp>A$HPmF>h4[kH[1=`%/&3dp?VEl=oTE@2?9n.CCPNa&#'O$&_+VSif'?=5q^B=K@q![0dQ>.?#6/74Sg$*(G>$R;QX-i*NidUJ88].[jml9-or/E"N':;jcnd5MPT,;1/cOoR\8n.pN),@`^:hR-KI1aC*`RTjn2HYu>.uRoj[6r/`Kt,BplW>dH:"u=RD9$]kf5@6h/WEY)3)<*QS>&h?%3NKei*d0a?R\r((T7q[@FS_m]2%aTiuSPVA]):,;9h291a0e'CV@T_C3bLV2A%I:d]+!IorqV6ZXgeo-T\D*,Zdbq>K1p\DXm];b?[b=85Sq;,lLbp'N[BMtM"qc=njJ`;CG%X"I\G32\2;D=/0?'aI],G&,DDZT`9hiSo"`,F!ni"LWgY%M>JOn>XgCApr<*B'e!MY.7H+-%([#7nWb4m3g_''$ATki,+:;kNu4A8uQQ"h2rKc.=380*?e^IA7JPKNjDMiMTfj_%FZGOZQLSLFqM0b3XJpM%XFut4]c,.]YaT+%Uk=H_Q1HM/XrI"r9%\cX3G:Rj&IjmMsG4+ZR'@"rendstream +endobj +80 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1115 +>> +stream +Gatm:gMYb*&:O:S%'\BbL)h91M#FkLXk9r$1L-0UBq%B+diB&#mN$.?>ZY4*lWGGiZ8L184j6W6f_]HK?/c7VhHf/r]t,'.-+1J`_/D.7XZX_N"7',#0@hJ"$IOI5gU[Bb#DK5p'heD[ub@Kgjc%Pdt;KRp\_96Ho$;S'GOP"dlf=Ur_:1/\#:E>Kt92!@s5R;X?<#0e5dL3Ej;RMI57Rl?1*s%$_GQ8gC\?b$6ns7kkj5rHGEUY-/Q;'2dVhob/+]bZmKUe6=kjM3D?e.n\3G\>;18"o&uA7]s\&/kfe$C,H:dS5UIP(sCH$EslJ(M?eIf2NgQre-O8/\k)6gk4?K+]L+:<6Oh,T^lO5PBXlqGja!+C$\U=aJBi+bMC([d5eNGA!e1c?fU96=``0&M_=nEAO/./Y7C>AipP?;\%/IqM'175a2Lm[m@umS>Ea$ScT3s]]1_0uhJ2`cdR(;0Xl#p`hfEdIt??';BWV!06;b&$DE`ogpo7gQ!IpNQg)?W_b3MiO.R?o`j0Pa&7/?\/&e4t(N3Q&6a'FtmoE(nZl/:c7[Wm&g%:pH'p#r6ppN<:gL:>?o(*[g#G'HA#AODoDjG:^,[.!dO).`^(ks\O(e.db\>cKj(bkLjBs&r]39d$'`^0*$JC#hWrkHElKkXPQDYR.MW.c3S.;cshk/u4%DF;@R$"K^c"[loVc$gT0XdQali'"bPSS04+*FG>EVWu1nG,*%G`VmW6k]YaMP]aQ$X9B?VKs'3R&=3V#L6#N^KG(Dd2mfo4[%]Jfr]@.Q>$pa@c'sF!n*GRVhCf!T:)!~>endstream +endobj +81 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 204 +>> +stream +GappW5mkI_&4Q=R`F#X'g*mc33s>f(NKO[&ACQE,0gl1@n$It"R)SuKO!3!c)Q&G.g0hK<:]kb?[8dU@a2Y'iU>P';@m6Y[l\-*@ML$]A=U@`NSI"4V<=c+,(QFIA\emGQp/nj<]!TCN(-^BfYh<;K@94N'hN7ONp][-L6?8It/$b2+kFBEDp2i0H\\_&:(Cq.W%sV\GJc~>endstream +endobj +82 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1410 +>> +stream +Gau1.>>s99'Z],,'PEie+l.^"SX[_b[1$oY/XHYPqne78+58!Jj@XI@o[E8kPF,""_eeOI_ViMd\c'f\`u^bta/f41*8j0i`udJ6HjF6O]'V,'?f=-&qnD6qmMl?`kk<^rE#R(:ZuAnpk[]aGoc@rdk-UBa?;PJR\;aZNk@`c;l4X"<>`Z)fTe!Xa4WiRAcMr:tiB;jD3Fgt1o@0GjfkSl<)C44Rb+S23*e3N=\SauAfm/$R$a-nK7rI:#L3b1c5(jHCa;9]:\tn\3R;K:'[J\"$Y"IZY*Jla&#,JBMm21s8;i*cg(de6;S^n?=QjPhW'n`a+$jEuPXuoC`&dp8sckS-)mo*'$O^n$1.JT-:M9q&H_AJ+c?o!=,$EAMm*nW$$N8f#^2+Q*nT6)Udq,XdpfEDO^!>9r.aNMeb,"$Bn\O:#f;ecVQ8d`nA#l@U*1icIO^3atXA-BMNJ/((&]'Q&,@_I!V$FJZEF_$n7LTnAW(]E.Yr1-(9_-QBX`VE*)kjD_TUtoFjpb`c+M):?%([6Z)NC+!*K4FAGdI(f>hJDp[jTN@01-(!q.^aFdC/q)@NFmBEnQK)\A\g,=jTY;\)/$sF)*5$^8oDVlARV,Lu"uAsbVt#Lc-OWuGV$bd1'X)Ir',Wo`L?I5I%j/5bJ0]#C5r!#eF+jkZT+ro(7[6LMi?3\!_(;'nh!;3u.M,_%U>rbpDa)p>+GWf6Qj6Md%nfqK')YJ35E)b0YM%<]P/!t4i5,"C:]ZG1+$3cNQ3Z0mE>"K,[c;Yr.#dI5MFeE;pn3@n./60X'$qcO[4q3Z$a7W--?m=CVT3cLZ1(8QM:KG*OE>H24aVKb%TjbuYfqU4gC;l(t(Un[[,WE3m*3WaLb5FG@fB7-lrCoV[:]9P[sZ.d$Pn!_#*dCkTQ9QH)`^WG$r>R77;mWOW[?Lq.Cgh]X^Htn;IrrCCSrcJ~>endstream +endobj +83 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 976 +>> +stream +Gatn%D,8n?&BE]&;k,[=4:'BfH#PfY0Bdq#)N4aF?<&IXEbu8&!fgLK\)01t?0R05(/h,sHB/"ih>QeY"tk=+opnhY\hUQf)))Qj&=Dq]&>-d1gj3Y=eOgF)#WsJl3YB'Q]%dYgS_=X!f:dtc_jC^X!=fWFqTfJCOGa+gW'pu.K.Uj\CN.W3*O*PQ?&3S^M;d=6amBWW@DqJH"Q9C0M+/"82T:.-,$76A2d*K9IhJX(kGk$hsYVhnC^W?rK$amd(^SlO>NR`HPp;3B,S?FWb0K8jj2Lc0hH96*;"o0XSmsk\4b+'dHWJrd^Qu@(f="h9Wa3h4X4u*]FM:Eq\M\cJ=.%$C6S;`$)R_VLi$V7cb[e<_LZXVsieqbI"g-\1'(l#~>endstream +endobj +84 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1189 +>> +stream +Gatn%D/\/e&BE]*;]IJ"8:j=]UEpj]Z,Fi>T^1O3hE(fG/"Iu_''YKW:k/?_-!U%0V68?Z^m9+UhTtrc-QTGUjj`a=$#mS.f7,j>\?K(dj'+\urdpWYk]u6*4i7QB^p0VI'%&AR@+PUNNp*rI],WD!2G;`r;Qa0dD!?TrgnS$\Y4>?l_Fd?qW)PB/BZ]8[3DY4r"J.,EjE^pL1WEoUOm@'j4Mg$oi7u`G&BUIO6+SZ"73+b3j'7HhN:k5Gl3$&t7JU%Q4oS@kjBQ/^=63^T\9$l1Zbmr.mr8FUl:`Irnrtq98Y#Ln)uH=?Dr^dg\`W]phF3.<4Pg8=#OKe*P)NJ,Xh]!2VD^*j^AY_W0j&`$nL3faME1J-T+05'<8;^bTL!GMZF;l8XP:;Z3H$57%p'UerbZIYUH/;'P:F:^MZl(u`4u^A1q:?nBe``8$T*3UB;+V$UCYU'>EP[lkCSpTs.*Jrtle1k,GT;4jeZk7B,HHroS[][%35B\Q2mu6"cSs\:cP8AMHBqSB!(9+9$\E,6_.(<2mG)!^`nA^Ye0%r,ZTC5LU5D]@,foh@PQ/6sR,e\]o],M(1)Ldrg`l\GS9OFX(N)eb6L>Kb?/_kn#"QVO8,j?"V@B[\p8EqFJ=reD3j%DuX0MIZL`^.9R*UDu7%^k%7KAU3<@f!nD+<3VLMN;s']F\.k1cpg?!R(KNYZS!bQ5/RhTLF6kd?h$?LM!iE,6'E~>endstream +endobj +85 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 825 +>> +stream +Gatm:?#Q2d'Re<2\;sqG;G?l.hR9N9.W+I>o!fL^gk&V3WGU(+^OFc[Xg2W6PDCEV#fAgg*Zq!^K91Ou\;D'[@f[BI$s3YO4g6o%U%e\U`U%Ta[SLKqAFUkbo0lDmcpGNMQGsdF;%hFL_.`QYs(G40tW9U7gYC(AGU,UL`_br!89/PEqqY(JWT2;&(jBMKZNBU-EdJ'![I"tf`/F;ta2E[qYE8V<;4MJ-QKBos]YXRZh3[9>G"3[1a*NXN2bCS:_YB7UXJ:At?i4Z/t?A\al>$7.#5&l4M\ERH0m\Qqo&dVBX4Z1NMFRo1\c,SENcqh0k7X*3oK4+>4hE#WP!NB_7GVs%+"F43/&DMrP(gq2.d9LUcLa`/MY;"9K*33+(?\Vu._)E?A;c';,AfLP(o:.2\)JX+O^ju,9]B87ZkNGS^9JVcHjn&]c\f20JE\#+7e>&d5V@W.Sl1/l9[kI37[_2MI1`CKA!qP?98)%OWNYLfD4K/%M;DTsOt"h-Q;rA4_W4?e0)>/UKL6;U1D\f)XA.uO?T4$RVu60rOB+C$\*Vr#+BkRbI%ja8eu@#$J"iSi2)bR3d!nr?WZ/'eF[qE.h&uO/6L^?LLG@(&T0"gLl^j24ksc7a!q[bX_0IYLHPaB8jDTr//PKT6s!EHGjKmA"FHTSljo1@:PS~>endstream +endobj +86 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1401 +>> +stream +GauI6?#LoG'Sc)J.uq+g>A[MTpe:+=CfWuaZdp'>MT)F'QD4_dV@F4DLL;WU+]g4U2re@#:dC[f_E79DnMBNo7Tn[HXk2bZN3r4.-SI;qI?iN0UddKE?2fO%dH2E"FRXBYM?SMH37Jr]?LJaiG?IW!i%Jnl][,PD]Y-n,DIQZftA9=jiGo_o47`V_=Tnhb>PJSB&r5T@\/c=/EtO+m8?#Po;iE\ZdW?M,bHmpI8!P0]&dXI&k_GF[C*:\J[9&R?.eQ.6'V-XZ@7+"9od(AYj"era^P$469-\K3pmR9C!!D!\]O5"_n-khU[Wi=::V/&aT6L;-H8Hc:T<,)?]bEM-?S5fRgAqB,7:HJ+Op'%oF:-0a!.7V\ckI0)eaO2]/B.bSX\PU6LC1k)(fG\c35jGlZo(H$,QjOnDBr,C\;.^\f)>'Q>N,@B;u*,d#MGm$\Ah/qV4U)#R0pIG:0"l4`9d.4tO=-ur&j*7P^Ghb2S"=OOWsZ)96`QGJN[Rf(/*M61t6YaYOneB1TKK6l9*DMXk4+T4ciU3+4bH\,I!Lop^-N+Ng!-a.KMlfi`[)#fjMr$-I[aa7raXTA'&J/j;a">:B/^<9cP(+2,&kMGN!?.8K<+:b0BH92e"nVKVf]a`Pt(id'mTVN/g"u<&4E_J5eFu(ZiXsg5;MF>PEOBg2)U]fQq:qO9`"0;AKn$NaC@ksZ[W!nW)_a~>endstream +endobj +87 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1297 +>> +stream +Gatm:9lo;`&A@sBF:K`e.P".Y82=C0Yl7a0nST=6EoJhq2:`6nZ[jAqbh>BhF)@WoD!.?'tNjP96gBJ#,pS5jr_$@"6:VrZejYQDdoW_n/eSM-@e&=`1MG=$dNJSF9l@\Pe]7E$(9"/bF1[XX";L!-h(\";UTiY,T@!UcJ)$Kn%pj(_`g5qm.O;8n@&S;s%!!kaTk*sk$[iYJ)n!P&HYlFS.+giNG[;'g&NG6sV;Ids-78[)d"C%<(A`9P%F1HY/%I58^758]n&MQogfFl2s.Ia4aV[\mQsrSOlFYCNCNom4_GYC^:Jb?IN)>ct2/XQ8B<)8f7c?.%5M[R,\m[dSlsC)!mLUYmnD.pm8[[k@=gV*p=l^2M3-sn]`pd'?A"nNYCZGmo]eFj/590)QF_.ei?ZP8Kom@(069^KZ5s)qBt/%"<^6;Ji+pm/\RBV&*>.e["^IC?>jdD[)3hhN-UbDZ')+?:T/fX5H=pGrSqs+0+$Y;2.TBX@1X8]eo%rH`sgGdE'ES?c&]jP-H"H[I<$$OSAlYY7-2iMpotY]B`HOUW`);SS2Y-0aT_Q*pT-P:R'>rhO+?jUDr']o@m"HL:..B8el+@TMJqSG!CqpRQ=OX>*jR6mF)#VNUMBd`mAFa"@Ee,T$n,$E1`7T[&_QPbRheElE_b['M&eEmRB"4MgTCM8E9_<\^QYU3cZpVN<"*t\a*VuMh34*h$(C-70Tp!(QK$^"OY'33l>4'FaP)[#cB1V?2VJKPc>tr[MNT&Qt@s8o+C`cM\UhSABl8>HaWDG6]]`I8Od#K!*L5\fn9!!(E9#l!B<36k9&l:HiBk&Z#&"PTU6,!6;b)ci-10_M"e^"KmmV=j&h\!J;+l5+8IR(K@\__.%`'PbcPO/TW?,G:0_>dAi~>endstream +endobj +88 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1066 +>> +stream +GauI695i9E&BF88'Q^6Q5kY#>^nm82MC8q5,q_?bf[FB1m7l3%5*5R-bcZRi%<3ZAO@YDlp$V`ip-7TB$P%SjF3I7,f.#?"0";su=5X9`!9j_J:'W][GBmhk5jZNlO:2ek1"[F<]FqM\8msnqOprCPURVg-X^@8S3[B$VP*QV$dAnWqU)2\lqFFQn=[B/lPr"j"n!G1i.HCWt\Ys8Ugdh>;o%D3!OFDiS\XrMi4`AZ&oM^Oq$X2+GL7EBRdEl`cD?cEm`_d;"jb41MZ_A`9iiqI;4rX/XMH^`Wj5-KRAmAb6%O.F/F")"3`(Ii`.`o$n&UG6sN#08ScQYme\d6^G7@A4/DVJnr`08u2ASJ>@Lf5Z,1_dp7doGFFHZRS=%-f>lJ*%%%+1@f"e_8*r7`qYQFPA(Y,++PIRilJ="1N>:6s^O.icHF3q2WU40VRC4Ykn*!B[X!sQ0tiX?hA@QL@$p#>=5>`Z>!!KtV1"e$L2\"`]a2^DYWUeg-guQ70<&09JsT_Z:m8@U2)SATEaECi"*s+l=D;9;Qt+Q$Hm$C;@l.4sg)4oEs/B0,dk1nuo`q%-fep.M`9+5=NJ;3TK6Wk*CMWU!GZ^j0O*Ik0DuDNL<_`FEI^[*r>EMFQhn+Y*Y%Gl+endstream +endobj +89 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 297 +>> +stream +Gat=bYti1j&;KpA`BUd:9&-:'aTa.E9a@W;/B2[g+c_KJ9(;O6!EEk>L?bS`8cRUj+$gg'9O/LnJ`6.iS\sA7r.h3r6jOC4(4[2Z'DFTR/aP]MY2\endstream +endobj +90 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1369 +>> +stream +Gau0CgN):C&:O:SFR^!6E`bs9"MEDo4)o*q]5(?18ej"3e=[,r$p-9)P-7[-#V[GnlX3Xj6(uKol(6A%cZjD'+4#@*)8PVN5!4)Jq(S=hf5A!)pcI7A0>:e.oCg`WB!f!BWtoL*Q(((_LKbP=:rI=JU4aJE9WmjtL4@,1B,g[dIELOIE%h-s]5\\4/##lfSr;acBHJ:Idf60U0$`Esc(g@)E1?GUk$e[Ia]uYP537Nqn0fW@Bqt5@"8=iUEe1I'R'`BN,uY'uT-5nEA2[5'"U_YG\RNFkN124]0rP3.mga%.:DbXOt95.$:6%T@INMB7\$=Q+,m[jRurpkGL%t\V9lW\FALsoOijk*`ZT6"#;Ag_oNE3X+CYDL8hPZlhm/Wo%=HKI1eZH.:kDbb@80o)A4kL]D\OtDo8HXXDUo:#2.)XW*k?m<(sN[j5i](H%E`7cO?B;$0,u]^Jbq_J/N_RfTZYZH?7;5d=_-HVZC8aQ=g&V.n4hZJX4\02fOU>d!Q8Qsf*\'U[YJk]LE^0'D!:ECcN-.>fN3%[ZZt5UMYXV);XT_j%hV`83I?%FP`]UX\p/93DlCPdrs*W$B3AP>1\^WlcD[j4F1Er`u'fH#MH;)5+1[SoM4*PX[d$EEZP8i<$4)5gN3U=.)Xh4%oC>J>[,di@d$:D,]3\;&V(<.;9F$$h#AK&>dSIP?R7;%O4Zc,$!2qF1e2)fZ.]XQ/b8$S_NM=in^4N7K5Z'KIM1MU@P9`LH0bc!4P#!!hqh!pG/7dV1C/],eiVo8_rrJJh%Tue['6RkF#^sA1bY9\,.>SZA;/#RdK2)\HU46ZL@:PN15]fbaoj8WrJ$ko&K1t<,Wrb"f(X\A;P/KgfdY4/#!eZe-!:,/5ZKa[e#,^3E'8PMR/FsKOendstream +endobj +91 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1131 +>> +stream +Gau0BD/\-!&H88.0iE-A;RMOup_E$Wcp2r?8kW*cT9-.n?CYm"?*Z@[l*%j))[lr;[$RAH^j'I(*mZt?lrmfVa""j!%,fA=Y23>i&XY/dZ4.abmpS>j\A,8VSt6Iam23PZpp^A7Anb)`"g56C_TahE7DSK;:qdFhHUFQH9Z/Vad2alB<3-ge)2mncO(\kf6.8QRo1PI!L"T+ip_/+FaiQaBM90Kg\H+^@Cfm"dg)_*L1%C[jqC_<.Nd%_.Eg6%>^cnS`_3F-bm!SXrZ8>0:-S-k0,s"g(6A65nP2c$X2uWpks)P%XHZ]R2\g>T7=l24G>uQZa-.Fj\+b6h:\WSOF`5V"HE-$5AN)f-oN'@u4\GCLY'6,Y4?nA=ic<-mb?%U&XP"FbJM?/:(L];ljXa]OJk3oJ'kGr8RP=\A'^Y]'%J6Lq%AonUH4g'TY3g8a7RbYj?$FLJEuDF!CM-/0[-D:rfPMrJ358+`G,!mH`G\n4HP.An9"K+:(@bVT(rQ`P-!A\9G_^qJ82kmXG`oh-m2G%eq&=R5gVVi8'\?56H*UH[&A?nfifIbo`P\KTeRY;8GSnP`)>P1O[(%'jQ.D>'QY3g.VAL;RhY)5!Zo-S/Y&N)[HVEDbU9dD+;-J/uQ.[/q$g$*endstream +endobj +92 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1047 +>> +stream +GatU2gMWKG&:O:SN)V*DWqs#Z%G4Q#E_kE!F]4k]9@3qJ]T%JQ=`TZ&nJuRbCgrTc?jWsZm%1OrcL)*n0)\oOG!gd)#Fc#u4$LpV>)*[bGu6FRp09a@%P(S'l\'P]11J^i:@i1.[\W16)j[q$YJ\BR8lpV?jh3plJsZ-dCc2@(;'qH59B+KF)nK>B,1`k]$%$*YX6tJ^.4IW3hiQ,Nhh\84.C:m9J'kqI/?4\7C3McXB^#KB9<%oYX188$Y?hoYi(G7ibpUooH[fre$@qg=CN:+'jt;;n.$T:1PXSOuUkd!;.t[7p!r,fi0qQ.-4h-T,Me'PkEJ5GmH\`8lq=3JO>m3ZF*TQGHJ7n:$,XG?H\5X1-EW8?^3-pt;9?`8[1t-9B+>_5QtCB3-e%RUrV*q3EDMjW'jQR9Rq:cZ!2PhNoFGqYFVti(TpWi"P;kW@>L%9Q0U2H*0'V8%?:jZFMVB#.:XUiDZLY."mhE,l`Dn5M?GASdUXp>endstream +endobj +93 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1389 +>> +stream +Gatm;gN)"=&:O:SoNTD&6S*,lI`'g`;N&"(6Y1&6r%a3L1!=6bb`'?ulaKA.>b"@;a@-gJ92(uG5(-0VJ.$Z,ffMt0Y/g&-/qBSqj!b,5H4._[\/h2i579AnL0Y'hQh>7jZ:hs*$hYVJ409(ZNV3PSYMSMe)IXRYHq&kpHfpG'3kI\Uk%FRTCX%/=DO=oIAeB?Dc\+[R;M-'"r#^mtn?KaVlS*c4gl$ZE?$I5>aMUk)5o-W^_gT77*dk.?Q,S#o?g[TTY2D>U1(l'kIs#Ma7`<.u$$SF1&T^:>;Ue!ZMu(Dhc=QUSKPH-m`Bk`L4eCjbBP)erf?$A=1N;=(+OnmplV*`p;MMLk"t,"J$GmANWA1=kN#Zn>:LA9b.I39YnVLASj&g8C6L?jEM!hsi!ZfNLt4%>cUhtm8fc!?EWgeJ%Zbe'/]:nEq"C,N+#4(.BJKnc2Z$Cb`S>@G+1I.^5GE9O22jfp'U(RaQa43QI0DKs^THpK-Ia%-1fmYj&[SUF8hh]=V)E8^b_>(5<8_B4I3<7QB'tq#`_;VRM.D!/Er6SgNGECP6AXt?fX5jheDVLn1^BO':TIc\1X7+9Upga&L*TbORQ(@:*l,+2&gQV$_+=@S-<*B"Eo-QsS;V",e21OMd7TF?1%m@C@iDi$!#%?oM5#-?L>sfWSDhiPT7^$'Oc-#Qf,_83idMQE1SNDh9,bO#.q-?Ln`hF[W/`?Di@pdl?-o^eh9R=#::!OH#Vpm!;',kJ=8:Vj`;Y)n]^Jd-%$TZUmPt/Q[*r:'dc?%)Z/ImC-`l2-%lJ@-S)kLDbB59-_BABbHJ@/u6s9$W#q##/)#VB<[-5J(JmU1E3OeS#?dIZsIqrV3nc~>endstream +endobj +94 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1386 +>> +stream +Gau0CgMYb8&:O:S9G&+<$u?ib$^8`4"BZrC12"n&\_)NMI6I<`8-,lA\@D4ICP*^[f4fED^P<@!ak%30f\%?3^YNf<#/^J4TS8\9;"4j_oucUS?1?>)?(S[/D/X/okF)e.)LTMW:71!uH,(t;D-0P.)=%MD^OY'dEI1h75R:4<<10FaM<7%e>G\ng/?.K0l4&1b$=scP)]3I[@F'lL^J_n,)g)":?n@.GS>"<"W:hs5#p,1D=6/8>k'H.f5+K<8pE7cLjcMM]iP?`p"r3!VL/UeAF)E[b#9G:m;8)J7$-19J_[?fmI\Xsh@9lKHl\3kl/cLcS&AL.d')u'RZXe%B#ef&7@7@%`Fb%'@A"u-UqL,lr%0b/oSlYu!CNUk\7p$ep8s*-KCmZA3h's23Tr1=:K#OZ8NRd:lLDAph1uTnFTfr?BYP7aM8jS#Hdb$UmK9r_=`t`3??ZTsGq`qpNWp-TW?Qru$ll7e3Vs+So[u<.Ph0-M8XQ%`HShpc_c/n7D!GgSj_$ktllHW+!RVl]JEi6R6B6u8d[=BN#Ym]D`$/=#]d]Ekcn!1cG?L%1o/p4beda9(q)T.aZG04u4T4.-1,>GaAnBDg5bSK3Rt!I%M1^VMpP>,Se%%h#(ZZk,-"_g:mbUY`!:4@>@_l_4884d>\?jendstream +endobj +95 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1107 +>> +stream +GatU3?#SFN'RfGR\1a-2[!!^JP"'el/cF>#`ZTW/KM6Cm?B^c1%NYN@]1sc]FLW5$&e`jPF1CDgA&j-I]q"XVaoK^Spi>JHj$G^/a#if6lpEEgGW]qG4$,WJg%j301-)#b3pUfc+KJ*lb>PJle@9]`*U&D,iHYJL/XaUhGm*)E#X92oTCdF3FaE2?1fmM/",Tr"1T!%L"")\cU"q&?hMGUUHf,ibhg?dGZpG7/&/tTu/hdamZ?Q<6qUO1RQH(FHom/.(0gqBBl]aVD4Jlk'Q6[(VVM3gZ3(I+lbp+3@W$RMSeddpBa>$\Q*f/:7-G41]a'Z=6h3,6gWU6jTr_K<*L,n$hbQFn6Xlc3GV&M#o.B0:#`5_qEL-Bl,O-Tf`C1[h@)r1ZiQJh_(Psj)N7H-IRKt(R\6?>S)+N7S6N8_f^o8Gmibs`*cP6pLS^6Zj"2Y"%lB`"]M)UVId9n,?]gMAL]!]+Ra5ZJ>o2fTEI8lk`CAn^XML9?JI>[sQ;E]s%1+t^sg$jDPF2RaU"?-a@?),gf9!8TufW"hFp<#KD=9d\nc8lc.7>d5XY2cqFC!Q1JL3K:ZHfZRo>8rQM'(1'.2<%M2RN)2,d%HSm;&_TN(90Rc%!mXq@9X\GAS&]E!G^CAT`+YI#1au\h7)Nj_)kDYhB80L6)Rju;ElF;%_9k&!$9'<99t!u4`Os9"aOPp.69T8BEM\U!mW_D1`=nF@h2*n$DG7#2\DbQr6P?MIeeh9Afn?8.0;21PV:c3'eH6-kiaX:=gCp^&4YalGA2G#UAQ^ELCA1&qSejQh]%M`c`F<8rh(>hB\%Le%ZjaVtNsChHqt8AeZct5;mW".9Q]o\YaX4W)gB<5NiSpUq.H1LP^V85Zt;BV(APMt09hJq\ilUendstream +endobj +96 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 292 +>> +stream +Gar'$_/@+D&4H!dME+!,BcM[0f;eJm$^-"TfUKfZC^8"SipXJc/Ve'eYH8\g>HL<8-(!)*i(`2'6UFgM%HO\E6%.ag`)XlckFon(Ik9m]=S!&Cr[1_CVfW[8,,A7YXF-0VL\_7Q>VO\:=hRbbXs],q-@/J(lKhssna1@PE?O9[B@*jf52,i&0Po+J5hjJ14*R'*M)-&eE_J*m)sC\]:g[$P=E3tpW1u&X)aPk#\P+>G8b]Ga[n6H/qSn`_$\M^"3"85N4AXPm%NE["gh1a!(X!aYF$]^(endstream +endobj +97 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1230 +>> +stream +Gau1.?Z4[W'ZJu..INreFDg1>nQV0bCTqtTCCe=5][509`E=BBMK7it^I(TWicJW`.tI^'(2_pGH[kc23hg].U?Z#r6rtmbq]V\I8?31gj2%*`TBQ6RpK'"_^,_\3Cj[t)cS+GU"Vq;>1n0M<1`0!5nSBnM:PZ7_fkh7h7?@.LA_;?c?0f3&cM\>!>i+>aV3QmX/6uucN)kGVl3AF)$XUlILU0LEc`;btOeOlL:(^KgL-F&?oGIBBP0:VYN]:'d*Tb))@cPrk)\d$!d&8M5V#UWGIJ:R7joUXMuf?R8>5]E5:ZWtpaq;lY"7Jr36(pP+oUCo,MtWuR=Vp-hOAF<7gsSm8p-$!Jft?!>G*eJV[SiQ<>qG'"oBrOZ`$UDpnOU2[PWb5G)oK3FU*;4j,\VJ%GQGPs"l=d_=9VbEQ02H^ug(_[^0B0hN<#WP-1[E5^P.k-uDH9o`u(mgnq)e65>)'tAb0+L8V-RZ6$M>25>EUW^((,+_1u6i4,GsA5&h*$Vbmr[)R,_;EJQUQNO@L[%I.j;g+_i@'$gW->h1jZmEoDqJ\Y*=5e7(\ceffFhKk($T"f?7P+T`oL9I`ZHB9cr+8Z(JI@9H]&4+*f)LC_,0j2@`J.XRB@C"$L!r'oN3]DY4K\qD_U;,dgq>;r@;,8%s3%lk,7kXO3aQG1O)l=Hu(=>asV%)pK9/QRQWo-t\&+iB"6<#Oh:q::Gu)7pl3]pKP[h`%Ps/<\PNVbC4\Ee>lZ\9o<8>bD?Dps^KDTuFIGSRqqV,XgGonnhTdo&JoRHPJooO^5g+SWP6.-6d1P-T[N_rmaAg6_oerLdBN&KA,U#Z=oGWG&B;=&0?RVf)(O/SM8g%7[0=7#EXO(;BQgr(pai;=A>#%QdaE`U(WbPT__hERm]'up~>endstream +endobj +98 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1102 +>> +stream +Gatm:mr-o6&H1J#ihgH(SQ6Mqr.7_>ZsD(X`(&7)WDo;(:p5_>3Xui#lZYoC%@![R3fp<$B:n);]lu(0"2CZ,ocnuC%mF>5+;DHl&@6/(2h;A*kjaIFFuh68(?rDJho-67(^6Ru7sL>!ik1No+-B]j=-M&$l)`Mcgm.()2R/ADn;,%&[SP.fWgg?8)A?qtF"`88TbX\5#KH`lWD*7SXG-KM5REgEjeC9aA8Z12s@2]/Ff5(B.q!d#rmLqikg>Q#%a?m3YjL(=Co&n#)'+]ugE];l83`U/Lo/F'[nG9IT2kG0@aKlND?M0!MfoSm`>C(;>Z)Y#`F&kt]X8KKGqgMR'nk>/YF](Fq-2l+%H%\(WVg-+4l1KWVf%^p>fY_=Vq-FD7$!53055OP-h12g/u4KJ'Y)Up1Ot/F,U!W*`ihgKMYqp7'`pU'BBf1-og>KLfNWYJi/!==s<^dg3a<6=ne?+um:&:/[B7.\'KrD\cn^jhmMl?03MgfUeXh\LEpdU0QR;CW;4VS3NXaP1Yd=_-UL(Q=KYu(?a$@D`d(-rMk>d$0>%p>@hmG%EA@bh(_"FL5#$lbjrmr\KCj_ZC,iSkNb'os5.p$KlhJc'Qtj!Y3o(.h/nq=?8`iJ(=r,C`p5]l]>8aLZ8oX.>iR()]K`/n0#TMD]:)6$hf+hro5I-ujTg0;#2:9NC;_tsTsD8.-&]"Q)!3[2gRT\HYOBu[]:OqVe-K,5&<2W%HE;;g_NJF0;=4%[Th2>-]"@pJ=<)j,m!2bj[H)YF!MA<5`ZbVh>-\D+h&LG$&qZmn9\JF$~>endstream +endobj +99 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1161 +>> +stream +Gatn%lYkK2&AZ'TY;g7M7&MVpo,s_>*uKDu7I%JN)\Oo_$/Uh)[1+,iJ,IUNdEk4[Pm+.SqOIH=q-F2?r/g2^S-+.ui6*DIi]o=$i`RJ5/9Dt>RP7pI+ONrQ7boUq?bP>U]29^Fr=P7bOBn;\2?C\g5tPOVdh\:9C9l"`t5_7,RE='MGbjg5YoBuY$%i=P+=J8Zu+Q(BDc-86?XFPUUeCakZE5!^OD&PZ3G;Mkr)T'i1(O--c?7%jGDbg(bUqMZtp,Mtta>*45blG"RW-uiP@74)]R:]7o:FlmgH#imP.dXnOt2-_XI=bbN`CqYp88QRb)%Fnc.c#]a=c-(!VTk8aVKm4TGL8Xq1/'GL]qt%k((Ja5j^hAj4#a#rC.E<_tojhG^8niGV^>'k<;C!,A*WDXFf6%i*T#/n"B7R)-KF,\f\.5:qn0ac7B[bPI&g\)eWHS5,$JP`Co1dcK*,0sDCH,@r\NHiB+K]dO6A"7Y)QPi-r%DB@m\3agkd`2"=#36VLkEtM%.%u\;F/=lUeNI3e,0J1;X_0m"TtoQ=p68^.!k-%':0@PbTOq6hrqEn<$6TFZj=3m,uu]N-ul(_DL%8/J<@GI\]Vk#Wbi8&*!7=P@S#f7YFA*up%Mfb2Noa_`5VPsNr^k^'R[dF'q;=B9mMH044ncaH2Z/bmd)+`MnG8_Y>(GaO6(jpi;I40EKJ:V#rpE9?='>LFDGRMR?X1u9P^CYn3RMUG]5cD)Q,rM$QWkr3t5Uc;%"qT\b40=3;Eri3[.N~>endstream +endobj +100 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1254 +>> +stream +Gatn&;01_T'SYH9/++YH1qRNj!o\"6Ot"jjZc1ecljPP)/Sl/)@O7EorUi=Z'1/)=FE*'RLlnsf^%]/%.KOt5/#_1Ehn:@5mGe`ck:5^KbgAO:CbV'2n+mKYP[2B6'+]Sq2mW6>@nq=:uk7)'[T^(:/bIt^lKj9]uP="krOV*%r.IQ,haTEjgg8m#b`Mn8Z@i-Vo$j;@f"AOfYI$s7a0@$+qLSneY97jfRnJ=G,k[h%W*)aGNKECTgH`6"&&^A6-%KiS7A.f8+,a,=!&_Ig$(PJR)OB9$F8NUAf*[Fp&YALg_G8#TC=E`iCJf39q]6R-o`+'Wl11aS`Z#um>%bc.bkIi*T"2c-A$B?I8o`qLDV?Ws4'FWlcC3)*ig8MRkX7PcSQ/&SE[=8:==4NY#qS<#s0.a$c12uJ/E+GJ,?"YUMi`W~>endstream +endobj +101 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1144 +>> +stream +Gatm:gMYb*&:Ml+$rSX<'T"$V]%K=iZ>&#%:nu]GkY#1u$q4<8-.4(gGOtPjOq$):/-Tm*!=#@YE"6O+R3;SI+4C(n'&`3]0&@6a*[DU7hshlc#WKlQO!Cr9)9!-j`/3MoN#lYO)hVHd(S>S=Du?(Zn!(d>Jei;Yb]gS[BuR/MEWS&%PTgd"8deA[Mta!LYM=TpDV59B%9nWa9OSdQLu-s\pOL1VW45-4Xicpk3L<4:3QsI`['5U1AILMp:>\mr69jm,jSQV;''t0l#`1l4!lk[Rr8rZPkXE;fH@J;Q#(;F(F&bc4nUi5.oTBuN7[P@c_0F`?0Gk&.VD.pHfWRNqcZTVu\MK1'n9RB[g_[$[[G5p!pbsQt"Li*ihb9\X3bb+WLeO'WJH[ZV:Kf<'K[7":.BNn?Lt7!BO9:S..Rl?8RT>+WX+J6K[/(C&P`U&B:OX:t-M!,!\S_oWO'(OsJO9AoJaPNs,ml3BZqlAjbKYTn,*#"AEOQP[]$ic?)Ohaj]0!%W5m3be0c"5N_M:VUbr_YEK.5'EA/N(TijFr;=B<"3gCq/%3AaGHV+B'es.l+0:Kf"$YP+Nq`j:\b(5;Uq:^qEgjd2"0rZ11QY0>td_Wo;%3R1Q';B[()bjej9C;cpu)OHrY@I(c0UF!4Ana&5a4bSW*So/k_\P.*f&9Z*3Y#7Z9r:]pD-97t`ZE\c&&(mW9K>HE#ZJSF\f+PuaE@C-(+jcsGgYajB=o0HfZ,M(fiKc,40mH9!R=[A$mP(9+?-64S54G1TSF(Q5Hq2+'!Fp9@UqTO3bUO(/9*(?"[Mt?UN].UBbr?Q@"fij`r;;*a/0JL4D_]$Ac43kHl]PX+%hH"9@*e3rNC[i2V(UYF91=Dfg\\dNf;BM9,R3UfL)1]67,`'A+Ain#\@^D]ce\t]:I)bDrt";nMZ~>endstream +endobj +102 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 870 +>> +stream +Gatn%9iKe#&;KZL'ml_s0Xg#FE9!uN1W_lqQX4tMnm\ZP&kI$ks1W7f6BX7P_2N"^'._dbo^;.JGi7$1[@b)53(r6*M[,gakrj9B'0nU4bB3A5nm6e`-P2')P9c?FG0kiq0'I[NHbEE=$;bl=gbtDS"?Q>>`A.:PQ*;HF(,M\!7"--TKBRK75#nnhB8bCJU9Qst`"Ls1R2&>_?G+YBM)TU5#^4a_W6kkd_>ZFlI$(Ea%hNJ^2o9%[sAWQM1I3SZ+o$fn^5-L#PB,e_G8#6)Yb`4SC)MT.H.Jk7^,I&^nX+>'t+[\VNg+D\*i^#XJ]2,l$q6;k:WXeiiO.J]()kMDL6P'@dG&;>bdPE3+W)k8jZSREl1Wg>.O%nXFLm<43<\s0/HSLCIhB_K&Gi"*D*[O7CK_k\t$r1oKHikq#m7se+gafYu1'DJ8JB@Z;g0j,X,I/_P!2I=!*[[2S3*cP%36eBodUe?l3mrb5,5A]+$f:<&J"#T?C,4%&_Yrh1'3EO]XZ<"AY5OV9;@?o\Z.XmQIdbbT0le`EU5>W/0R4(XT]5eRifc-B]:M;T#2a!P0$u,+#nI?QVf?+r/RN%4f]H-+,;H[;"3J;$R$XdN1S.8>GH"6g?:=/:/2YOB0HrN#U+X*74JXGSR+@'.HFLF#Xd9fH>Q[S~>endstream +endobj +103 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1346 +>> +stream +Gatn%_/e9g&;KY&MS4,3FD?N*.#HO"Z/pokG3rCHHgq7?QR$#Q:]O=Po(64LP+B*0ieec$BLW&[rHJ"R^Bc!U:Y:'d$K0_IhHkjH7^1#XN/'-h/n)ITqJm;6<518W7LuIF,Lr1p::?!]2UN&R!%4B7CRZ9NDi\m8q_KZp7PtL?T+nUJYSA[Wd12J13]tu*_jd4]4\F5J$74Y'E35('jlk6`7#tND027&9dMTWtNK@f^/?1Sf(2c^-b\+.Qk$L]'A9m?AF#R3)X^ho#b+c7pQ1,o4!D?FdgL$uj1dJ,!NC=.9h*/NSZgqj_b,UY7HRJBAka--U8h1a<,S@1SWmQ@Z`[6lp&`OGPsG+=:'cU*uLkWl%[[GG@rF&GW3e-(as>HbjM3`r#_^t,=YX)PT\SSP!n6W*dGB*`m&hu8:0dGPG.=,"dA4N#6\ASiB1H1;`=(%rBrthQmIN1M1M)/_A*GjckE<]),,S"9;$&H#jGfaLo`L!t-WLPs*+-Nb159p")>Q_;0K/9N`qAR"W6)tbH?lu>Y5WTJo-j)/XlZU]@WLDai$_aqk^NOF#le^`-2/=@'uWVD2sS3,0L\nY2*B4S[E`K%MiMZ,gnl8#-LR?m9XE(Z>J:Kc2.Z6f'FP6pWHL[I2OF'G;qmL@n]RHF(\h3D%T,Le$][t%hB+toll:MW7e.8;@6"LY*e\Y%q$^XRk"pgT$75_K@]b:V/5P*epI*Me]HR?>a]6EIkK&,Z$J+ZF)iNJ:3eNW>;tX%[RNpI=T&qU;`Lgs);OGQanSX-tC**=f7jI:5>M)T-T%hH]rGURhVMK$tJ6aX%ee=LM:ET9^7Nl(mDBNrB'6(?1S~>endstream +endobj +104 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1216 +>> +stream +Gau0BhfIL2&:Vr4Z.#23Tq,Y/@q3CqU#@V@\Br0TGu8C/'l8_!!p][KDe5i,bloo:o^E:(^714m?t!QTn-"cd5@&Db-%FReb8H!VAA&GV8#L>rhhTYW5iEd`LcSbb;NU:`P0.?AMo6+tI4@[nnOOX+1C6o2Y,mid"/k_"H^MGG26pDYJl?4`iH&_iqIodJ4]]KjcpgrSReZ7)bHe>*4Fa].Qufke..F.C%H7;.Wj?@a-=`G#"9n0!F6-G=gd'jiPF;O)RSqCKP_pS;ZecBl&#*To@VDfXIa'mnM_@=P(JQl+FkUTC7V@0npKgaG+nct32$1"+"M]K%.C61:qrUZ,KPNrFJ?R7guiX$+KK_F"t6Pa^XGXB/L:S$3\)c_3#6K\!Jl1_$M.j8t4LWP]MnnRZ!)bcHu(j]"RQ-cPNb$`REMVCYMm)laJlLa%XDjeQT61AX[$/@Ck:Od5Ai-7ffplEl>pJ?W(%NRR(V4iA_\QPUc0s13uq8^\s*>QA&';Dj][MjRaSUVOh!$itkm`"Y2Q=^Q,[AO14?D`^2YVSE#3efebEs>\n:s36&'8=$/@O0KW]D`2KYNdi-I!E_@pmE:FGg,T,D~>endstream +endobj +105 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1117 +>> +stream +Gau1-gMYb8&;KZH'E]-TjJ&Jr&kNK#Kp0)]!#p),p8TTq%mM7]=^]noDSU9%(W'Cd#q(cIX,Z`Rn7OO%#VH4jA+K/7am*0;mJYJn`3:fMF+#b/R7Z!=0CWe-"u;b]K%6P%1Es,]V'2j`VUmRqKZ6QLP,5CuW5pa<3Ra_DeN&;46hjSKrUCVTl1O"\J_S!sl\/ge]:kqpQo0k/e#Y+XL\LP$,qLc#V9h<6aV0t185OUNhK>^J-1T!&##:A-G^=ah9Xc?Wl.bqfFlHTFG%rh,o;a1*d#st-q(KD%R)f_7jk7s-K./]1,*jR0dW1(Y?Je@FVW%3]2>X#(O^S^'rUf]cYU@?ade>NF%]K4'$_N@=knh(A1rk=qYQot\23=*X2si-!7sf,nY4m9Ht/l8*!>0$7uaT0:?L]-j(Yb9B*)ALc\-_u17k";r<*DE3-q[pDaRUL^Vo:Q=TikWEEm42b]U5qeSjNDmfNMH%[14>]bf*8qot)4q/!C>#@Epg>SpB$@o:1"-SD2XN5e>K=bt;nu5\T_;IEqi=n7_&dKPR&6gLN^?,MKZ]=5%$Q/@.&]q\~>endstream +endobj +106 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 422 +>> +stream +GatUnd8%P4'Sc)N'`X/LRXj].CiF(kA"j9s?$]G]%ViFq[,$[)]n`Qi0p%2oZAY:&Ic&G#(d`ONpD+K_S`+>'^`m:mfUtGoG)p>QX981HRA1/ZjZ0/(eDufW4Y7T0H/"3EL7Wk3#E)P7:VU4"4oPJBh>8_>"G%7X%L'HYDbXpP-RIFN-:B]S)`!1SGODe9FGn4'0O?@2:YR#PLj8]4GBtp2W#Ck/Q[7M3:%*kZ_Q;hG]4AUWH$`bcW*Q#nCpOen)`rRESrEknE3\9BbX9EJm5KXFMKiDlYi&978B7hG.YN`%gY?!dREA3~>endstream +endobj +107 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1334 +>> +stream +Gatm;;,?43&:WeDN-O%DBaH&%R[1G=eKJqAh!MD4cq1m;A=E;YfYMaRjknrTBhRE@Jb2nJg;NN/T'r.e-6\o,-Z&g[-,>iNS6gj[7^,u]3!gq9p]eJcNp_tFc,sQ$a^cI:B(Hm'5rXBL9W"!VJg`gXCVb*NNKP-#IRKZZHi#Hgg,l$7]=m%GUH"(f8nGCqLd&]8Dclqs_Yp.Rdu[G4(\4Z@nN9.j73M52)^056:+sU_IZL:@j#g[-g;n#+$Z;X(El&5=.%.?=+Y@[/_8(q4qBk0MPFkfGX-E\6d&JjOheHXen.e+!"Zj6SoSDGr`)rm&,Q4Z#P86u`RXP=dfc%rA.A0)9"q$q<5g*]XFCm^ZTMig'LMXGB?8K&(dO&!p-W1O;0PY;DQq#.oU1T]A)0iJGmcQ5Cb265bf+_TSkHIh@IoP+%bM1G*1kqWe13pasiM;Va8;1BAKV!DqJa%H*q'&NAsqfh[Ndg:G]d4#UT'U@\BMWJQ!eZc:PZ>p=/d%<]!ncO8-F=MQ]TumqWJ@M+39BjU]:g9T:N&js3rZBrtYUQcRnN`uFk/\Qn0Cc/"&lZA^jeV$sVt*)/KoFVgR1=d:JqLN/]/IQHpKR3\=)3P966+_?<76/s(J/(JKkR280(Cr4#oU#BR:KbDmf.9.<,D-FSu#idk*14FMkQIOio/"C1QM$GsL0522@`YEUH\XC/,cMNHlP1.4.G?iguE_VC\endstream +endobj +108 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1094 +>> +stream +Gau0B?#SFN'Rf.G>]lLb9pCePRZN:r?.WtZ>-/n"?IfIoNWF?\c"obf^YcFd;^e\*Kaf:Mpu@n1F7/]1TSr9SIF8bdI0TQr!9XGc9hZ)b/(G1&*kDC1pD[St"E=k'@`hS.ShjgKEeO`Ci1*^l[=tIdkp)9&TTR/>Ve>%MjDU2,VnViZ#[rLiJ1h+ld")QY_B(aTrc^]tQj-gMWd9S4=q]KrK>&.Q9k#?qq(di@t2O#W#@WqFS*fCQjJ>87I6M?WmfU0QLcVVHF%m7&:G]ce%@XMQ,?fQ8kP,]eIYl')$nllGEIY:S;,(dar832T;n`gE!$aOX9i"O0-N$i`FP?8bd;V<'>=k[Al@qZFQ%"+XM\tj"L_J%DQ&"M4aQ!9HYdA+I22Y1j1lg4[Z\nYN*(cFtAl>PCT2Km]1gGZ--[B#()1)&PD^cD)?bo>;^cc6ckh!GC-V`&PZrYa,-@PujinkcGE'(5%,c!P(*`N\l]DB`cm![/IV'E~>endstream +endobj +109 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1107 +>> +stream +GatUs>u03/'Sc)R.ulYbJmFV<+/Dli"_14m!n<4tXqEM8C#\+(_01?Fg4/""dR%/Oe2=@-WHRY"p%\2O!+#F*jY'C1^.dML>6$r"VK3"ISH2D;iI'@*BEa;A_\!lL#+af%abX!)5#)O3[7Qd-(epB+X`!s-M-I@=E`'"J@F1]B;qc[#,JX,S1\."W^uFqfk<`S[>Xo&^ajiOf"4p^"LcM,#R,XL-E\M,mg-_8,K'B1JICnpd.ei.5k5QFQT[*_m/?KL8M;0aSL(Q;Mhae7_"%LJW;5A5:V)]a'dO.K3*1LqC]5e=RJ^'SD[C2;WL5uY\AMH)R9oC)re4aj`NHdc"K'm8GnYOAQ!J5EH=pK0>95GJn,%Rg6`,_Y_i=N(;U_'66_Z#jls*^=iG'SEQdM.MHUNk8DBZgRu0e6o3MGgQDkc7RN4(%1aCp02o\35Q8hq;fgJ&-Lp`8YE[KJ1OJ4odrFT$AS34BHFll(AI[?Xb=0GIj>-6d)O%-l%-55eEBr"D<'B3Rq8sCiX=Xhq-H`2."4"pQGH'qPG'WHe>YB]6g6:G3A[&M*Bh[Gi%;8p7B8+W>J?8G2+PW(.UoHQek&=r.BelJKr%#@QrmsWGfKW>dpVNfrVp3$L`HP:@^a"^c_K]*iA*AnV"eJH!P:b.jBhh[e9GU0DKZ-IO>Yju"ekoK2mTNU+89n@:F=/?BSHJ@endstream +endobj +110 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 407 +>> +stream +GatU/Z#7E4&Dcpm2aRb*.3X8%PU]BXD>b$MUtcb,Z3(:=g50-m)Ko&Or!5Fqp$g^B))`BjYQOVOfAp:d80F`[anP@2gVTrn&?LSb%i&?%S'BY&Z>f+$;S-q~>endstream +endobj +111 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1366 +>> +stream +Gau0CgN):C&:O:SoHR,u@`2!nDm?H,lqAs$]dR1WcofB8+2`NYZ\5[77h@RTsYT$sTu!p\\YPj_I=F?-5)Rt-)CNMdK_gO1!TU[.Hl_b%)F1E99E5AuTPk9Km,$gf&BL]*=gh*D:\[>1Mc2]RpISKM&(/H.Urln5#5Z$k]aT\ZC;3\F=-hC4H!deFWNmLrp].D+QFPaui'2a^DQ!;`A4d.J:%DL,O=/Z$TP]D&_s&#&!INEtPSTZ][!YuEOU7i-o_FNjA=.lP79XlqMl3&aaG7bGOb%=;`%37u!@?MMs7>.9oe8]Bb!=Zpng_Cu9h!`sXkXcnld*0Xgs6t(ah1m]ge=OCWq\2T`&.6d8`I[o-B%U,&sZ7NlRP\eX-'WM#U,S`b&Z#:>O;"C=L+`,#]VV4.n<_*U+[S:8ITWC1VHqWRkf3(6&9N]=>#U>`nY*>;nk!RcK/7dfSi@*YA[oI%;aOace7U&Ln@AGM6\/e(rd]&g0jDI#>@DDe(]"Puuf"*ET9j>3REY1>.\6uY)r-6VFU_=`8DA%UGI4B1kmYUAS6b/j9VgmWN/__YT;*-lJ3g8#>P_8K'(mJ)_b>\BETGR6l@p*?%aaTs/)NroJ4/[;8i3^3<[DhpB1CuFeNF%i3[%s(!Nq\d9Skaojl1>cVdB0]pQFNs0Y.nsU34WGOaJcppN4SNaFcU_%'tTi;^0&MrB`0M$rJ5,5eZ3(.n2Qjlc-%1D`Issg"a6jW^1:B/3XP*!F(N-AJ%uN\YP&F]0fl`M!q,d5B%(,`u,&+MKhE(/G?#I#367%.2[ZSZ*3508`W+/9*F/ZWP42*dG_6B&ZVp4fn(`d1+DG`n\W`0oXiMp8-68iEs%Z\[/>;ZsA^$l.ZN(40^8hOZW>`.ZjA0^l+c=h6]eO)S.@CgJ/-!b+%Vdc3@!IiNW,,o3CE%iNN,apnE\q!O\iG#BU#SdleiM!(bJ)&j%Sbrl8YmLI~>endstream +endobj +112 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1012 +>> +stream +Gatm:gJ6Kg&:N^l7]Iq(775Z#j=T0@9$gK*A#*eC-co85fA0_!h66;"L9q/$6A$jMdGF$PR55W`6[Do69lbd%(_0:Y0F-St0Q5l8Hj1tgcHdMS^G:`jo[t@E7M5$\M.&Q@(H]!jXcHs'kKQl*50Bi7g7>N'KO\7qVMZ@iKl\mB!k$^6p]f^gQ)X6E[`ph-D9Q:!Q/3i'cB+qW=W3%L=Sa5<>&c'WKG]!kCFU1sCK$:lJdppdT7q9`XAq9N$&N/Z5b[UBcYp.c:8gJQ/R7b:>7;>V"##=Yit#j?&4D'_qG47O>i3\>,-\f36mkW90sS2+iS+_49&Nu.@MX+&nj^HRj>q'r%A-$%[hc@sF^8&mTt!/E#2puj`\\mNC:$o'/FC@>L#+^q>.e+nZ?#`^MBr&g=HgAY5"nX!'+t=[rmP'B]nh&K.seSD\LuhR9qb+!f-1m:*&k`5R9Ggs-OZo4'sC!d%6OO1fNZJ!M4$D8PS#QWEk=,p)nQ&@@]V-,i)F6#`o-NYhk18J39lr34'OVr@Si:Ch*"X]U:%#i'?P@"(W-HBW<^=W-D[lP=_%J`-U4OF;ACA?Gp\!lWd"Q["pM%4J0Y<,8bbn&K,Xno"X[\R,2J%[-h=`tTog11j1)1F?Ag#obf,V:B"@[R/LPh@[7F_io&2R!ak1UGXsl:"h[8p!XnQ78&aKS\rlpDj0/QS!OB3;`^#5>DlZ?bFDU9NsTWZ^27#`Uob=,DkmQEX<(M'0d`VDlZK!VIcRI2!;k53Z@8;k7LFK`BlHJ\U[h&Pf[V0+$SW7uT0f-UF/'cIN5c&2u^JuF.!7d'S!"[R'(fOfu"3XK>f$$0&r2[j"75j)gg8Bgjk;dni"Z)?bB~>endstream +endobj +113 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 907 +>> +stream +Gau0Bmn_[l&H0m]ERH\C9I%LOjb.OS0b'+:d.JV]'qB&TQ8lj,)>s6@UnpoAUo`enWG6eR]pF(i04s.jc0A)%!1XPWpaZI/_-dBsDupk]_YN-JrdZS$necVmS2a8Qg-GbK%d#1Y#'Vcr;up:R]aEmJ?,4b.@[c>PZqp0aPa_)N-Tl!Y/#:A#5[Mdbr"WPt\6JHs,[B?B%<6kf.c9b&/rZ3l3GHJ#VWELEFb),r+>mSd$?^)pWLuL<.C3(ADUMjV(6t/!%cmfX,E96-Ps?I.I'fge7D<#Wa<`ERjj?fk7>k"DPU0]c;O*9I+Ag/&YnV'%@C,T7'YRu;0ZKlH5rRc,Ml=cV4(5.)"1]gb?V9.tb,dPr_!O]m[rdP7Vs%#3hMTJQj`b`.5qG#:D55P2Y_:o+Coq@+]mL5I3$9=8YX^tSpHF:#=pG8ej[-42^@7r3&(l0Y@c*),=b/aeje@O.+f9mDUjdFOAYEXL4\j>=6'g@GK`_[B9fXa;oC+7l]+UG_D)LW;FX^Ia4?_cdgNS[Ij4W=:m(Q70%Uqa6tQ-o>g3ftC0X8BD4<54p$B.n^@O4qu5c#*/m`p\,8]"aV!RODD94?9PZPA2GRqQ8@DnT-Re?SSUF:/endstream +endobj +114 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 871 +>> +stream +Gatm:9lJ`N&A@Zck1E]Ap%k05;OU4C)CZalp9Ig*Z]2XF]5&,WlT8DdW"OB40L>hlZ/=;]Dr(nTJ6pI5M]1MX!FL=1?m-4W@.?(.iYPi9PNN8,rA2V?94lX8TB118jR19h.,/*TScnJ5/gp9n[R[')f0)aa:r3_&dp,Nr"g?is^"1F4CK/kiIYQhm'ONm.GOMbrh84n1(>R&%H,29po'BYA!OZf%<)$ND@DQ&?PY-eP$U(S?)?p/GRPL2*EjM[da0mBS:endstream +endobj +115 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 399 +>> +stream +GatUn:J\k^%)q]6'^n$.Zo-[3G.W_?d%jr44`W!NgBP&W8^[Xs*dPCQ?%fKp[`GM&9Y;j\05k?9:m\QXaB^d;,(od%.NNLXi?j`u0:.pE%/.u#T?]\Ug^^#mlctID*FLendstream +endobj +116 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1402 +>> +stream +Gatm;9lK&M&A@7.9FV^%>pN0Y>Q2\q$*p[aD4=6.jH9m&N@`WpW?iuTrq@F*ZB7jsY#d4a"Gt'9hRn+io??-M5-4Suk5_u4\GXR$ojOEn)to"?rrVrdZYsKX3B<\HGW>(b-IW;"#JK=U2BE=Jd-F2'oW1!?N;DQ?bjp]F@rLe-kB(p?DQWC$2g6L(n:sb2M(ni5o=n`5abTacTC5/@a)=:NOR<8I4UZ#+fn&V?Ki/p+).ADap3VeEhtmO":K*P:A4j1^(4VYPaHtt:4lUB99?mei#mGLm/N7?!B!)SgjQErXNenco:&&6Y!Zltn[,,F?eBT3:V#%%b#mhPWC1"?]0Nhk<02nm7H:rF-ksj@ThVD,@QU73W^Joh"9CtD7mI1'),3M$N(`1]*G64m$;Hem_5Kj04V79M9gpU\k)#,A>#&C>g?UY?OE8C-kar)GYdQ?_!\mU`V%cQErEIBTQ+fb]A^^Zg$Eal!JRMd1*3J6?'!?pQ&Z[(&iuB@2dL&TlGa_M?%FbIcT6*gq8WiQOM%64h1p8B6cQNAETlHQsb;9YU^VeO-pQKlINZlr-jt)KeU]!"R1kqT4UffpLK4%L(kYHeYd$50PKH($B>$>gp_CR@=_1OchS>Oc'MLBQ=D42:M9#2=qaHG%>=Z`aqFUDfkaQ1G^*>[$1>p1E\$.&A+mG_)^s1KoKC$lg\_a>Uh3Eb]Aqg2h:geC*Dhp)]/GLua_")Jq34j^^8[&"`:n-+G),:)gX0?K_!ia4fG@KGTY%#Z9pnS0NXh0ddK6(>]VjYHJ3,X*:=]-Yl%NO)*ARni40V+t(&C@9:,RPAGQLR\ZC^P*,t_5af>A[`bpWFot*Zn[5cAejaJ2&XWKW(ckGH[b=SAqQDUib7\&jG&=;8K+G)Htl,[]\%M+"P5&E;7j;h0_79V`U&Bj3XtN[I8-[f$V=:\7)Z_5Qdst&.s$&7(-u,>FcJu"4Q5WK%<`c,~>endstream +endobj +117 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1081 +>> +stream +Gatn%@;jUK&;Isg=.D)j%0RhLdjD38LXHr8dB6se84\r=OBak7&f9NgT\Lu^s"n9G/=_6*>b501L,.fdL7muKgBTOBJ-?OA)JZp;QO!Caqm+NjQ\6HE/3rh(?-+$K(jsgp$/=#_s@/0mo^.kT"]=aC\XMU(IDW3Gn@mZ9^rg^JgjbVgM/@h&M/4M'/CV]d<,@H@2OkI_)0"7`aK03&O`1%,#^0k[SlIR#3]Q9%bs:Zr7f`eFben@0j)DElf+6B-EH7Ijm;C$r+c4N#2X1ep3]%A049/k?@9>Q?Vs4lb?;YQ>\D71oY[DLHk;M7)DTLV#l:]L-N\oTB4*f/=FHCi)$>(j3W4:JR8lXu\Kcuf`\P3c)FL&eK[gRoNoOsqEIV@T@bFdp@]`6Ak+0q,1WKN7^Vpf/G,0X);p"8W#re)8j\R*M\BET`BUOq?\-QUVnP)?h,SV-7r&[LD&iX'0\VFClDr?%5YOrs&5oOPHdC')/6h`<#;cJ..&sq/tC"HT%PfCrhJi"tK72nfjN.ZLZ[=Yo@VW-9Wk?+0VMC=CFR$+RJ]T^HLND-Fc+[2DT:MB$;Dg74U"Ep:3hQXVc[F?N;\O8dObC`!;mCq,Adsm=g8;(-K[\D3!;Q*B7\a4P7],$a#$dAX@W@g-Cm\@/.c*q^`q4S?Tib>&SB3_OC6!D1$29763J7K?/@Y/S<`-]I')jfYCG"Be2)bi4P(,@]F=dGB81k@As@$h2JP7*4_+d9PP7Np!XXVBN2u$roIZ?*VPq(>k[%aWk_\^b%48[;OMr(W@1SmF'"&u>NW,)(SGWF]qYFb~>endstream +endobj +118 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 949 +>> +stream +Gatn%CN#[r'SaBc81@:LL,uqh,[g!5nN"X=9u,7#;Lc_)^h!f7hZsc$_Dr1JE$2fIRrIpV+e(Ei&(IG1mg$Sp0mVPiZJDtRD.;'*@PQ9JP#b/8SM_m/X^Y9Kdq:urgbOm&ni9t:$-n'GhAM!]+oG4m42;E7^%S:sI)'erZ7d5q>@$mkaB[@Q1cS]oC$ppoU:bXHPeB\QLpFPSNYaGriV/tC%\7YU@=+Vk:7k+AQ36sA@8YfJ6j_PLRp3P\>m>Of"6HDtTo)r0:%X9-j2/b9=@/P+`[V,LaOY3Ma7>XNWHX\&l@lq@EH$BTESc#Ml+V:TW[=9dCmO7VKi1G$H]*D+s9lO](E9\Kr?bpZA^XR1\_Q=,hC^cdLIC\o^(uJZ^l6$XEg&8\F$t`gF:HlS3g#VPF-7S/\@#@IM3%aRd@h+RmujnGGkY&LUT:5HG(l4ZbnQLmQELUa7ag1k`G"Hqg#6DQTb,j)I]un#u1mmlVp;FKZ07[0@>>jSI-]B+11*"h[5A3W~>endstream +endobj +119 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 880 +>> +stream +GauHJmq^Z3&H/38iZJZ`Z+6%q6[$1\=U0S=[]A)q'S\(fWJ58dZ4`K'IV5QQ)E2io\L[ad,l-:Yh;*FO5k%F9V^qg3VJu*o!bIJ=J-K<[kuTi=8%0^hGEU"^K?.PB'&sq+2s45dN%K^(.Q\GIN(A$:c:8`;rZt#VnKLL3N#k5h#EQR.NdV%ST/!-ST?\d&>2G'5.`fk^Y0/g>9&bH*MWu&,hiPS.]s84"FqH`@Zrm2(Q=YE$-(S7nCq":P*i1t+3-Zu"\E65"`-Bp,Y'r9s0tad9]eu4;iL=jgbA+=QZ3?%-]=:7+n:8]UHR6Hccr`'O5dL[kMK026Ff?iE1ROV)9op+WjUn^??rKuTEqMr72-`a`&MNk!#pm9HN!t9M=Gm#fGU2^glB!Bs=8&WD:.p_eG9os\W%o#]S$Kd>_C4ar(\km@itSGW.NMbI/(!qr&p"Me?\Hc"P+^""(eU>J.nL838K#(6';XBrfC]8#qtYNXH`oC6!/E-=&0u\4p8Eu?rAPo[Dl7tWp5<*mXW*6?E63k%rO=Yo1L!31L1?#XLD=+;IcedRNI8Ti.YbSqAjH-hG3_Sr7T:'>coTTMQQg"*-M;\giSn#ABuAR21sF+L:TFG<'GX6)T,f#:ECCq(k*qt^Hp:ETpY3"c@NkPUY<,![Ot"B-.ld8=']>IqQ&*&Ubu0F^)7&bU"0P:;Rn`IcSR[)Prn7ZNZWI_O^u)s?]:!k@2Ba"rp&2jR!>*d9go-r7q~>endstream +endobj +120 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 784 +>> +stream +Gatn$?Z4XP'ZJu*'_el'2B&(_UMm4f$)q4(jH5cW\HGuj9l5O&55fB9`Y_qX0NUdN@F4iGSo[[E"S?0ZHdMh@cJ\*u"3)M=!"4Q*AMh%$jsbUYpOl2Ae38`*i^7*X22l0MZ,L7%gR]l*(Q[X!1p`BL-i]9u9[O1k;5Di>p%/,me,)ju#7.M$DH6*`S!j^cmK8EUgt=i$19sB2Ba&pN\@W&QXV6,b#:-3u0eu_pn^lo9,t&2?HO=)m`%%(ZnIm*rE"P@%eM0\]h0PZIOo)Rf/7:C%P_?>[anhXTQ*b@=`a"FHalL.0n:WP$I9[?(b'M]/=#6Z2oHOC(9lcDAhE%php?7=RjP9SI`U\#/>&R(L/,!bOsBZ0Y/.j,Es/6s[_R.Zj\`r*MJ1ss9C==ig1RLL%C[A_oT$,a9>MhaLFHP2$5#5(7re7]Y/ki<&_8p&WRo[?c<;C"r`LrpTs;3W,t-_+e6Uhk-Q'/YAXo'+-jZ_QN~>endstream +endobj +121 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1392 +>> +stream +GatUs=`<%S&:i[0.qYF50"+M#>F(J^V?]$3[+hB/('X9G8gB0I7B/K:lJM&dP+;\4e(AVI`M,3P2oB&,Vt>iDG?+fN/E#SV"AoSc!i"q($B$EZ^Gu(0'Fdj8F%E3Aj8&Uh:2eLr3/Z/;1kfq>SC5+@Cd*d+"E\oW_P.;+(H'JGbaDfON9?k(L"@RS2'/6H[A-2oMDJo)0DJ9Q84==IE\:!udJaaMC7=RLDk-/3hZ:R.OU6'iD2$Xu*88ki1O0q9WD=TL!j3OWgpZB"+Sc`%*MhPPU6>m#bI"g.'f%S57u*IWK.QcLm5/dKKqFV&Dn!Z;LB1+5UT"AVDfhGSS2du_dbYg[!CAN`[)BY;aaos5`VU%aX:4B21Z=YDZ%[>79G@a3c9*M>1aI)C*6T#/re#]@CUhHH2#+,Op)r&55l(t(SKB'*@DX1?\pW0$WO@,GbRdQ8n^'AIbBLV4;N8%+BV%Q;&RX?L7.3E'0["(BOZ,YP)eWD@=o%6:385r4kLu,9`AooVpkk`1V(pquAlqODTB($gXiJV8?XH]B._PK*%XK2`+00Z=+GZa0j5pIciKB>n\-+(g/C;r8F&LI)`911X@h-h)ST;JLkon6-+5riYZ\NYA5nV?-C%F?D*6J`HOC-#6Z`_:i6n;oB=]JG+t>^*bVL.Y_5'HQ%I/nAZ@4K[t\R\/rDB!O?QgKj[\1IXSK2T@F.PJa>EfhV+X&BBqF)tHFJ;5*-db'ep+EB?'d)U*HBZV?sB/f]Vub?T:mYsYdO+BOuck*rpi<=\I9#B3>D,cUV&1rg>+5._cQLQVBf+5Z\[[BF,!LKW^)Y5)tnR4Pp6busKg!JEfnesquO*&Zrprj;3/QJc&HP.7BA$$@H)5;TsLqL1Cs%!/&53OWutiUh)LlO'X;O#h9A2\5omokK-p'97JW2#ghX%?:&DSW#,R%PadP?FhHc+)f[TN-3qPfpC\gEZ6=:fng2>IePb#Y\U*J87rIfpgoZ_N\'g4J"MG\,<5NA02!4B~>endstream +endobj +122 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1134 +>> +stream +Gau1-D/Yn7&BE\k;iFs]8tqWUCCDIdV9:0E'If0mf"F"bL%\o5pBh;;XOHd3m^m[FquC@sF@mHEfOUD@hou"2=9^C+R(^L"^opZr]4&K79]s2Ra+NUs]Up8DH,S90"sF+3E5M*,,R[dpm=J)oaHg\P@j+Neg5F]mF*kTQj/X@W2(h^*ne#;fnNt?"A8nM9[]3k\!?Z;6($;I=#u(ALkp[V0(WeGMbq7=jTMLoqh%B$+NVf%>^h^pJ0GaFi!"PX2oKUA,I3))U!cthjGtj<8>R_m)13sh-V+.tr.]fXn(l,X"8_=D0p7G#6DcirEY%?RXA]B<%DC"1'#dZKR]n!U`n84$i[4m:uX)r`d[qS$a,,qAp,,\ZTZ81l/5Z^U\aW%'I(p'EG.:d*G>JXquleaQ/-:83*F8d/64/(*!8?McEQ!"f9cro9jb8KENJmqK+-IsDog%"sP1oo(V29=IAu7\1tXYI+oGUk\`43l[r<_$7SfYtP)Rl2rYi/l(T8k+XAPiU!i6,2P=)`!q)=Xi8cpmq.>u,g4Kd+^?Ab:eiU4'-%aEV$(bO`c,_1l>[7kNL26mpar#;EefU*]*:3KT%J"tDiF+:eZK:Nn@c(t7ic%8K/NN;1s?0Z;Fm1,\VG*6A`3De9Ae#J>D*;G0kQ6g'1R^V4]X5'E.1lcS#oJXTWbb.:4g214>+$8s2Kp#842sGjmY45m(8l9_Rfp*WE=!cmT90=3/<%kX8\f1_s4C()'ljbogpNue.HVY^p"t&O0A\-EQ/b1"kT="O`]4j1\3SP*BQ0br7^FW%6+E?SkOq_nIFA#C7M;7XI,2*M2P#qR@hI^QZG5>m.NJ6_3Z)rdYS>";PPL>,n?r3orEp4:JFpX]YM3WPd744AO5PLn3$4?f3;Z=G5QurKt/gV0WU?nZUrendstream +endobj +123 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1011 +>> +stream +Gatm:mn_[l&H1J#ie4\<+;A9D9i@cYA,O]8kZ"5PL:P14/B,Ab%J#Q/WLB(?o,!o;S6S\r!_JY^3ia#WFsPlO8/mD`Yq\rp`12J&:tg'jP'lH[Ag.rtJ?JiM=b-C30YYW=ZFf_IA?H$KWger_JE*Y]f?YXd@@jU@AZJ>c1d.-1r.eDmRilfR"VnN7oD7[1`m"XY%o:)*1I4Yn_:(B[CZ"H@q("1<`Zji?TiUD-r("mr[Ft2V?MolNLSnbjS[Ic:ri'BHJD(]fqOc*@rgBboG?28hen%E7OL5Ss3H*Stm>"Lendstream +endobj +124 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 417 +>> +stream +GatU.9i&Y\%))+2p]q.bAfnD?a>>.f/kA-GaV(cdb"(V?PP4Hb4.'DB6Xm?cK,q`$lt6>&LDX@Z!$5pcHP.GJ:iAVC`\XQ#?r`8r,OtT9XIu)AEE+S?mbgDd'W;mhY;jBRJ3-"2"4X+O\p>$$$.#c\kg1\R_0(D_qIk$X2DgE`br+#>:uCoHY8fcp-]:WbQ\7+;.#X7+5mN[%)5p\ohj>aS9I>"'d`Z6!GZII7?D;!GP?_<*>endstream +endobj +125 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1200 +>> +stream +Gau1-gN"2m&;KZP'Rc/sZF.t5*'BTM6:dE"D7g:(k8Q-j(^5o;8fCsql*j/jF%Aci5U`WlBkI3[l0H)GJi^Rhs.8?QmjIVf`sACD9P+jDhR-a3#D:.K^K8hVb.%On,C\8/gihlk>+f)of;D"WR,n8FNZQ^>he%>F%V?3YAC-qW7h9RfL&Q8E$0MIXZ"pt5$(+*BrniP@@KDt*6Idlj#='hB/r(f*IVUqN+H@&H19:R5$V'M[5jKiT;`&/'Sf9GiGNj#M"UX,WlIW3mft)0OGa3_D6I.$+@p#nJdBr=0TbP9,&g84>:J^&PAAN<<'c'?3<)m-4#@>a$C>HO^*3fJVGrqee:R/u8o*6h9%k+\SPsrU.RC2J0N?VgV9-O:Bji%7(I%3cH:<1a6B7DXKCfc#9UhQO'Oe@j+r=V"h@k,["!m>&E^G)>;.4-BQ\.2i,U>2(2!oEX?bC19_D20n&KT18U'tMY\fA!34b^c+9Vd9mKsYY99BNOaUNZUP1MJ0U27(.B,`YTQ/;pBocRM1=bup3IEQd,sEi*M^T$.]Eq5GXa-c3SFq1H!*KAVM1,%/2sNrP))`H$&2pT>ssEiVXPrEP]SoTmtDrD%F1nm2D^-Z1Ik8:23Q3_Oo40pj*b?-.eOo&PZ[4&F%50i[@](5IUPoa,b$^?Kt.'cBK5$o@!$YF"rUbQ_k0&.qX=S[?%[Ns"*p=nY4Q"P9lqF14XkMW%P6(\cdDRVkF;"eAqJFV6fh0#jm0c9N@;htF]rsA;T'W__1INDJ_BN*hcpYYO+hMnUV]U)(WUp=eUM<-pF?~>endstream +endobj +126 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1225 +>> +stream +Gatn%D/\E'&BE\k;iGg57A;G/f2EukBNYjVn'%6T?#rS%I:qq4Gd:1>K*7>(h>,=>J,Waj*;[2,2kL2"8DqNM(L.K6.Cai]LZAKIak>9%p[uWVL+%#qEI8MM$G,2\2q72Qf"127>Y+#-rLsbHcQW)mab9onK0J$$hUO%dI3C$l90Z@^3K\"59L9o/S::PkAl;;+>qrQK7>,_/3Pp^]W>hP$kS0tX;9[rlE"I&H/$Nq9bMYfsQ@^_9g9@Sp$;I4_EPg@8j6YXniXO=Q^t9:Rmg%kEb5\?C8d8HX8f4R&#!K'reF5U0Olpe;9plU(eCQ4JekJ>8.""@\`=a=[rZo[P*QbT'/XkXh\/*r#bq"U%NP*uJ)hi_DD(fkl>!%[10+.DVi`%M'<;fG`AP5LBNKS2ZLJPVrAn\&%t^ic^`]Shjb7h?.1%N-2pU.DT]NVU;j\po@_?LaF_QNAT[pflT1()(Z&-Q#OkA<>_"Hu+r@@'0f"JKBSNX+8&hVAI`AQHFI_K`pnAG$c:n?hb\\T!VH!4aWD9*43<+jR1Y"TYBTJ:5@mtW9e\j10#"R^q:Kq):r`c03Los+r!h*>E55DBdf0Z=NVpF(L?IRWb"**=[nM#d%*geMVl]W6n'8CJ!^0nmu&B(JHGlcB<$ECt\i:h8(aG`VW'WGtSi=.4Il5/5s!h#>!>hf:FH(R2.7RAfI[k8E!8UXkL,:#:'"lNcb-JmPF!U6+oCXa4hal]Q?,*gZ"9kpH>sIG;$7,@.o,OdluaB?P3&+#nZt.c9-Z=LR$>NG9n%2I^q1.2\U4KjR"iHX2=#=t>T[*Uf6V>tJ4t(TfSfE!&&6%nC_>a94_:;dR-A][5IW3pD@V[@sW8*?4F79J\b+4ip84K$Gk:.&%g6,'q'S7GDq4IeU;FZ/SB9hIsQ:)aT#`pP8uDhj0RSF'MNSjc:F)V?%3K4QNi:c#Cc=aW24"\^iIendstream +endobj +127 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 997 +>> +stream +Gatm:>Ar4L'RoMS3%pK(8e#e.oSrMj,hfXNRl"gi9q=Z@R`*DVFR7WJ3%9f0_G"tb$&KLH55T_o_n`+(Fe?;JpmAqVOob.sSH0+g!7D0YUdmY1f^gcV"QGM'8MX!'+10sL$`Z1\s0D$8ZpE5\j`XjRHDC5l=WG=ljKbeicEgQm\iDreQ7>X6bM<)D]92s,W_8Hj0];i8/:RE@",P3UGN_i8MPN2oC(FQaM(n-aqkX;eE2-tQ7h$r*e.(5oR,6Bm-]&C(tf$/bqQA`_3+cr^4F5ubqi;FFG$;8iGL`Ts?CfE(1i(.UY!NYf;!cA@_*Mo1#U]00Vakgks#6gQmRMeXS=Q.6k*=p7irQ.^d<7D";2W'oD3a,&8ipL87YS'/IauO=o"d$T2qoQR,t7(KdV?Ec3PQ^C]KD.T8OJ1]-O]1%FeQ)U2CKJ\`_\2]oc-UnpsDJ4[Q,n7a`tj%#.eV)V<5S2A9?Z:,f+PJhB)#%@;1i7R%/f.s?%R+qlA9Q33\F3il&+B8^)-!fbn@FpRefaUd:O#6K[,T-ITLNN*XS>g%Lt,/qVse=EG)@5XptQRj9:2l9J3Y260EBj-`Na7'V,O@#^O4TXFt[!Ut8'p3J02hb#5h"4r,T\a^2&N+uK'hVg?/$_tE)I&dD'K\)6HGB15rdG'k+7FP'2!jo4n*j\J&mEA.N8#)FqDsa(TJ9';h.STgOIA>OO>Z%.VAO]1&O]@W^L0[C^RSFoig>2F@pYW:t$gd#@I_sm%:f?J])f2oqb&!'[=PVdQ.*R+G5e6bI\k0s,-lmfNJC2VTnngFZRU@G4IcIA^"\4%BW)bcp~>endstream +endobj +128 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 574 +>> +stream +GatUp9lHd\&;KZL'm"1AQ.R]2/iQh-%8tZN$--;``["lA8WB^6rq[+8GLc)\BXPno66:KFQ^N&j5]G-0.;OHP3R$q@:^21OeG"@3+!JmA0/NIrAAfVuW5n]c`u>j<6br,&GaJD)LC*6e.Pdf/IA2W?N?ZF_-Rr4V79?H(&gOF*RjqkQ/g:5qq,Paf`q:^Ch+hB2o_rh.gkeY8'AR@+a5G:aM'SNV9OM3*n"8ANM-"G_l#Lf`C(!8LAC/mOM[U&pDP`8SYRjljoFh/=uE>qoY649\1")1mrf#;TLm6c6-g,P0AM8^Q:o"eV(6]?F)ktr@QIKU]"#jus]%p9VEV4*oS1UQc,e:[%&\UI5=ku427g$/l%X5n/dX@IDqn1.cFpSS^DX(5jiO">t'^1jp/h8S+5VVd>@OZb7=QXeHqOA0J][>dVb.B8uQFng\`o\7a3;6WinJChr<6#'i(K%1GRoIibX-]Osf#ORatS]*>:W)=QkXDt#VY<24QGN\FZ'DNY!!6#q:QDheQg!hFI(lH[lGf:?UH_itH.(cKDEQ!G!5)G!H47"P.2u~>endstream +endobj +129 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 286 +>> +stream +Gar'#9i&Y\%#46L(%2G<%UBa*)E=lmV+IWI,N9bBG7$qU=N[HA*qhf`5Wee2&/HKi$')X7FiAgT\h];5#P1[endstream +endobj +130 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1236 +>> +stream +Gau1.?#LoG'Sc)P'h9cSSQ&=8@E=PcFXc"lk$o&o>ufn(GW^p$O%jI%&c]Sc6PA$na.\GAUa1K2kNdD#3s<.i,5"`4!dm]#r0dWM6(.n4cXKF`@%;E>mZ?UsaUjV[E&J\B%e0l'U0E+U(*.dgltr)T\5a'\Yhj-Y2i=Km;*)8&KB*B:IneD^mHMmjcg_H\,nMYIRdDrcA;c.j6^B(-l;3;WD/\1Cg%eieBL2K"2QcRerSX/s>;oZMNIZ/"2]E+VY/e)6c/G:DEDs(@s&pU,^+!$8C6r=rF=[/;7R;=7B1.>aL:0a5?YPI[F?,7A\9ZamJHUV3:%6RjC&-i!pjF5CaVH*JY@g3OT1eYQac^1@2sGj=8Z2Mi0l2$f_27Z@7[18pI;5'Q+#6eDV[Vi-G.5[V=N1((JTQF4/lel+=q.S*/V@4Kd?#&&(=6/Pb))QmdkKFuJ"E61V`d)q^UA,_7WHa#/W4EpjPhX&k"Z:,#'YK+EFn^pIEo8X'o8U\G(5"\#/aK#Lb,Hq.e_dn$j@cVpjNe-j*0Ya1Z`W[5X^QCr:1%]!4$:nU\fZ*73A9&hf+P2_A0Fp1PZ&qcfQQ,2p27Bu_lFs[&+I`C6SSQZ.KJ#P>.9(0'BN^2`OJ<%05;6YM/>&H5(N?L+")a!)[gpRhXG8hF')(Xs?MVBSEmiC>OW`OWZ&O$!RTgM(an]P,#>]XI[ZK$*p($`]MATOrjP35bC?<5q(Z,%lu&08^p?dqa2M+ce16&J#?]uoc01!FXfg/]IBSoebU]Xg:pQ)F\jNA#6=EL0X'hTmo0Vj@,+G%Hn=O0c3)6)AM+7,^E'u='JBRn17Y#eX0Oo@2b_e8hOo4aFFSgg];0VfB?rqK_enYIKCk:='gi?]WP[]>L\?A[`b6%87_]sL44ot0%W'D1(3&C(+l)iL&.Q:5YcsPiS%7`)c,J2+Bbsrb30$Lh2M1PCl=kn\Y1J\rM-?YmHlHBTJ1DGZ`Uoh&OI^o"F!=HS69tp'+_k6^\ioXl(*n'd!eDo,SJD^S$O*\\0ohH#>=(4]EI`d.moAXL@!~>endstream +endobj +131 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1154 +>> +stream +Gau0B?#SIU'Re<2\7dV4)%p*%[ghsQ>n_$>K"8;Hk/!^eCRKf#H6hV'./MhF,rqtG7l!,D1*FEo-1")\/d(P:cD2RQ[R'KDfDKAUJB:uacjN:?][eQ1Vk?GuQ9$^MlgB2s%W-eS_?RBnT##r`&g0np3*)^UfH_$$>tXJE",`Ma:N`]fK5i*V)s(RaGt5piW@3/7;^q=aN7'(ikg42!0([??4pH^E=Xgl'ZY,3N\G;aY\Xl#$ojI#!S)R5SeBld^p0AY"`j49(S$8?Rrh23Qk+#Z"!L(\^P^7@f_-"l:Q*c(q&UH]gsgTcSjDMo'Nf8bFJ=Ehls&>7qp.G8#XID`YqQ,i'Kk-;SX-%6g)jg@&Df91@MY.i^=V0>0G.^W#l4`$WAT#:W$`3L@$uR9`&T+*K>Aq?j,OW*Xf[k^&eBiOWH!QS\2]*-?o*("CGOW!1^pCu>u(eW/6&?A&'H3WsBqLS.U6Xld7NU%a+k`l-'PC\:QII^gED$Z&8T4ikKd_UW/ER%ofkPta*)Sk!]1#ST*-8*&/Q[0%?b^_Odq(jE&2!fHP@@Og]Rrf13,RC3KC<:"':b_2EhQZ!/)A"RJQ@&Y*;iCTE09D:=M0>`E`m.`T5oFNW@p!)PZ4mL7?WKO4W/MhgGr^A2ur\#nU:eJB9P.2Q"1+l#=0$G'5lPgNDHbjB0YA!T'.]\n`+_Qj$XPCs=_IGJS/LWaYBa29R2el`&PU4CS9a[AqEHNp4\UAi_EiRN,T2"2^-^aT~>endstream +endobj +132 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1164 +>> +stream +Gb!$FD3*[7&BE]".7U>kK5-[7&0S(((Ne?B79Y"ZTmbBh-PJT"?6&-EB+_9Bh)fe1gZ9T\8uW^S6fRY2n'#^i`eQI$I+K\Hi4D!so@=.2J9_KNk_1^?K_p&T;o@Krp*8dT"##T;&WUOP3E7Vq8e>JLSJ"@0D.ff5%J7%I'O]DX=`4B?LCdV_OV_0h:"Wd@AZ>ET4gWpk=E/2Rg;(2eo.M\_rsJA$/&PFIPbh3@Mi?t%gpFt;TGh]+"hT>;CbeRqgq%nkb$JGjN23P\hRVS?'%Yj>+H]:$N#40g0T%+tZ001q2%c`7;&^6/bE>fM2IYC">B0!$!4;b(W5i4_3.5k[GC">a`\)%Fjjn\1DS'jEh>TIuNshfo&Hu?9@[Ok'Ek/V/3#_>-d?>4Eo12`_SAn:toRrQG%->&C-Ge>\\NDW?7Q_[dYt;b@^Yb.s'"uJC!%'`Ddf#E0V"0^5,X+q\Z(Qr3#GmncjYIqTJj:K0Riam/)iZ\iFNih@D]o,(W\I'DRe%E:MKtG?B:`IjUN;BnTR@.(@o6c1=IAN\6!=+E%-#)E]WDB\$N384M2W%d.[(%k8PpmjF)1d!i*Ik3i.9rGZ])Y!7'O'u[IjgCF,>u?`GCAPiH\5h7>[pkR,](;:-\P9e_9JHQV9V"\6,IR7KFRlEbX^U-SX4dHL9cHcAMWM#Xp(!mKE%i?c&7J!<;r_U(?^T?KUM]1/TR^"d0*r"':!K]bBYu;Pm'Nc\Z^$h4Qc>b5F-6D&p?B)'3+.endstream +endobj +133 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 744 +>> +stream +Gatm9bAQ&g&A7ljp=L.:/*_!/ncT$$734M`_GD2@/O_-7jWsg$Xs>AK&m-d'63%1F'PGLXR:QrW_hM#F!%/M/]jSWbW:#55o+\3CE*gd1Ao,)b.&"_fH!)alqI=^IAh$AYV]KHt0O\$=g6TDrdGE-(&e-7/O7DH^.ge8nT_:m`Q1P8u`EIR%^9\*JCKk;^K=.uTO3sOLE.TU>=.c2_"kAs2k4Lf"N.pcsDX'u:$T0uU,">1m7gDrDfHLdYK1!85!Wb5LF^>tL`?m5S6+>meCq.N33'"a;9)<16N@,p5X!K#]fb91eg7F_j/Jkd%P#,$BX(7ff4'^s-'mWuu4O,/I2UBQ/0!]]^UQKptdMW3FOKVK^[8CQ/%(Dgdhij"^GV:9WL"tO=-.PiBQ>hI]paHhY=b\&icl!(]$h!r_e`A&USqfQOms=&PH]M4>jWn6&#!#!WCUTs]P;mP%Jog60.@h0Z4k(`V'I8VlFO%nQ3AjO[Bdrg#0;glMGrq9Crg.b&9oX,Jn\OH)q7<01oJB;9P)=mTn?Qc1fhgBh1M5WtA4[P&;0T!n7IGIaIcj60U9=eMgn:+KJRg.U5,&4ok#tTu9$.KfoTJY%?WE6LHWt!CI^Cuh@#s2(S/Qk7P:(EZ472SaXP2`eMe;5Z',b@#F<)ZDMAWj/1_FJ,ARARO4Af!+,:U2XB1a&_4&!]GsH/)"k#c?#2N2oj''%VXo;`%([Yg!Y5~>endstream +endobj +134 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 930 +>> +stream +Gatn%;0/Hc&BE],'`3.,R3Fp"g,E6@BckI820[9)N`FG:8]$tOqhO3N/5+^j=rW\[o^>XZp[&0MHNF!.XI6?E<iE`E#.@N(pL&k>.!d)o(G'V5d\IJ3RuZm3o@`>]C7QoN^jYN_tEtA31`H$HjFZ1PAglhdD`USm12]g?Qj`MkQJV)h%0DAmLCt#i3A's"_S';ljl[j$c/Zk5$P9kAk^$C4N^bP>V+g5i!D3^HN.OU$[$5P*Ub#M7>UEC5$1S3`\GXE4NHn/khXLR<:n+\M3?2DC:M`mTXmr.`[doeS@oi_*P3)JG]f]ti-uMf:XLPmg%!!d^Jg'@,mS)?fhe?>m>Ac[."2fdLHUknX>Nr9jQ%:u3E7gfaXG?kQ0Y>'ag#H92>SW^$aG'4aR'tUV?(S-UsMd\-.%mq<#`73_\Hn4\qoV>XjeK=F`0#lM7Gp]M:eZCCu7e6to^Ga10?9)d2DK:3dIfWH+a>Bk3'=Yt.c)QKXhrE^DWD2fca6;8dD6J16t/_3Rc&o@d65;9c.Y!1f/I=aeDsi&b/\J>4\^9=PcEVHRKPE==LZKI!KB,]6o/frLl;nC=mO8hoLe!ZMl@@e1fkBZDL.A8WWacE_T$.6U>IANGIm[o,n%L5gooat_(NDWuL],OQ/5%MMbbZ,`hB.[M%HG&+eMgA2=*/d3fX7psu/X?X%Y=BXud]Za8S!eb%%Z-udnYa5aE%%k@)a$`:H7LaAr%=A'.crqcCF!PHsnn^#&8]mGR1>j@[[Mh,?1`!&L(3,m@Zb5:ddl?/8,t9)V[Q]Q829K=0BU`K([?iTa.-)!C8;p25df2$h0-$8abBTlQcpIaI;PobTX7j$<~>endstream +endobj +135 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 975 +>> +stream +Gatm:D/7l[&H:NnEDQl(7\%Qg).TnI.1F5;engrcX_KAC3a5Kn3M$3Kc?AJ6DJe$(%)KYJGPBmn6=>Z]WICTVi:@:&(BfO'X#1I?EaeVU(9i!Sh3HRd=_)@qqgb-=!M)>W2^W'fRh$0;TBV;D&,`7T72_S7Q?;\qAPfP1Oo*%_:9!UU`fkXSHIk0k7`GA:$Y8PY-1"F/_sr])G=$TGs1P@0W-^TOHE&r%MrL[Km1e2ZV%pVC8eKd'j,G=VT=G$6./f(TWG1Z/D/>=<^be#8Udh[1q0g,r<(M@d"&?01.tjKq-h3O^Dk#fmV.%DMG4YgKlP(WH1`8/.8f'Ouh1\h%0T?4DG#5ad?4T)9+i+`B1)fcXla@T-VgE9PbR7sQdS'jopQ!k]?lsWsVV!6#SA-[MoR=u)ljC#M6b]1jH75)huclrQA@[)]iet?unM"+dlX`JS(8H@cSj0J!9q`D?\M+n/&*r4`Mg+V$SQStg_#I3&(G\SC""Y6]>YLWWa"63/fe!^^IfXQ'W3Z~>endstream +endobj +136 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1066 +>> +stream +GauHJ9lJcG&A@7.$rAL:()pK-[u]:NB`f''GN#9_!Y%e`D71(VP*D1.q]4(ACHIZK?)=e++#kP',NnUA0F6lPra1liY@fQ'@),*r/V(g8(hOlJmHios$#=0c3FiF8jnU1"'bgq$^ar8p/_b/5)jHho!l%afCioN-AJna=RTj++^%nY/h[Q6e4M^"Q0e_cZ6A.u/4!d"C5tpAJlLXTA+i3cGqoMCC2eaDhmTu!!<^$4!NB*j=2C0;?M#f&U*#-LG_aq-Yq1kj(kkOHA"tkV_^r@=1tXJk8REF%H);[/8\'@mXk"bW@nB4dTT`<9&%SblX8!5'._*GJ09#.t>LAFI[/hOL=E>__[fUnJ-@%Y.acp#GMt8M6;/g<35c4'JV8sSgL-r<*!\IYM[t:[(X'X.4*<%.`0X>`["+PWA07bp3CZLHoJE#[QPBSN$$.r1UoW@kdpQ+gB37#22`9ErXHu>%0Iu2l?>lrjgXSeQ:KKS6R)O`,GbZ_O1i4Lp`K=7LeEVTR@T&r^bPhT6h,"e8GD]/H)l^o..7Ls@6P%Qcl\6=BN&s7E'EB<5Bs_I!0_5`DHOr@2l(/7cakmC.=,?\eo#k*G'jnhce;@:_o3/3<"I;<"`)&+"ZQ[92XkAsTad%@fC&m.0n[9BWfV(,$GD\`)6!?($&osDWVhB5CU@suGm4mso'AA\qMrQ'\5ZJqm`'I^S_lu4e8m2-GUFqY\DuuGZ\Uq"jDqm\MIrYB)b#urObkoCn8E^VNcK&^`Ta@L;UYA1VQ$uHQB@fn9r8Sul(eh$RbQDfDGGC$hq`5&2['!s'6,B-l#,saHWIUo=Y/60f"DX\$F)_KL'DsF9&<6qROTV2k/@k?1:gepqRi5mBd^)@ljQ>1;c3OpG,qM3C-CmWd+>sYfqaN?Gs=a'o`$ig#&O7?Pg4lR?DCrJMo1D[-iX5g&rf!~>endstream +endobj +137 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 911 +>> +stream +Gatn%Dbo+A&BjP%=SDrC4NAZHd40pPu\la&[I(*V=0:64ZJua]MW@G/J8X63Jn7hEk@#_PBhl9ALL12K,)*#CqNh2uDJDZiR<9iD?2V`L'>:qH'X\!kK7#j>2O42^f0rO%Ej[No04$ZPYRA#@%Vc/DWeM!t!RrY*O\Mn['K9qcrJ(ih%`MB`t&(RQ$T,K3+<=5JdZ#8fsHhendstream +endobj +138 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 960 +>> +stream +Gau0BbAQ&g&A70V('ju;"lbDcqp5CYdM>,iE"fXioKl2dM2=X#'P5mGhkf[%)p(O56@]&_WL`#7m#hbsH]Z,uN+J][1JNR6gOb"*i@3AQe>md";*6egf6D^ur`+B9=S-F3,:IARcJD#U\..eaO\:I$I9u8l3UYmf*cA&>le_S`bUDcoN_8]Sp[M#JtDtDc`8jkg905u7%q-e2t7Ba].QM.0&ggDaXW9JPTS:Z!MX08la#d"dF$rLc\bo3[i;l('uQ&Ba?=:gELG.k?"*U[`\$7.bXUs1$b2p0rhpKs^k/i'X]0TKC2Ou8UG`$/W$O+c)gmDWr2DWTYsm4%IYaNs.WU:9>,(.>%Y?l`g!`dU^/e1fp[=Bq2iCS9#!$sK+"&`6-N5oF0[iQ3Y.-3Rc+)o^C?@Pg](Xp`[![Z].n\^Hg4tT0lOI#';ZQ&>=&_lDR^tAc*"Qh=7^#,,iiT2@QFF\J_*364M72C36jJ^ZdXsQ?B^P[dVb^35@Vtna2=d5?OdDQ/7(04X.]B[61oV7uRFLOmg-1g`VPE%D/5=1!&5>[pFc:9jWN%H&%N-NaQ%KP)3F3dRQYc!&]N8_2PCi+X>1<<\oahLt-K,6/+."@`(Uk()%L(PP38n-r)RVTUR#9()(`^lIrakB+2#g#fT$lsmh#XBpKbBBM',]h/BI3%&*\=YP?Y1)Ls20-FaL/D(BZ("p:O>24np*CVjCUsVDF?2R`dLbKh[>7]TipCctF;A&fH*Oo24Fbn7]g(uPlWa~>endstream +endobj +139 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 532 +>> +stream +GatU/_/>bs'YNm99U/8^C'gbq@J@39V,F.cC)*`ig)M+12UF>T`Y_bSI14k"D5&<]A;LE7IP^TP$q'4h)npS$I*?R"M1J+:[U>#$Ja<`1g$E.ernL1F,V]\TFIDtA^(:bXHU%+?fi'uiTK9LgLK7:0kkl5i&q&;oD!cnAE)aGIha?c)eqgt`MsNC.H&qaFTDqc`jV62`-`k4ZF9Ur%."s5Zf2t@Bm1iWsST(FQUC>"M$A4>#"A[A%fO.mV`[T2(\GQh]u][9\D`"q`d%0e_e6Y?[X*'+4:Y/MMUEpu\fr<&[Wt_2b2!Ch^PIbb8%F;#Ot2c(!&fPXRa_[WP1oY"h\fk!pZ362S1^Z=rug$p%QBs(=1E(gG.9G6MQX_+^)f^endstream +endobj +140 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1304 +>> +stream +Gau1.DfaVl&BV3,YMjNsD[_hSGVsaKnQ1PHn1js?cXtsUV^ZL_`GMgmdW`D5.%o^:oPqirMB1,(hgm"Bn]So(#Cs<3%XDV]J"t4KM#ZX)Yd7,_D)?aY3jXub_)pjke$+MVb[,%%pip$pPCj+60OT]?]dGF$^l)SN#-.C!cQCP6;C='>=Q5]H9IWpDbBh5;^$j!SjT'd+*-,#g`%n.(REKoo-UDX"0`Rl]bYc$T9s(:k,;NMBX?,[`5KMSkNDq7__ZTt^jNEDMpdjpiC[1fR+t='n;a9.oRLSM2UcoV22uT&e/\)h1_g6KN?K1DWXOtuNkiH'Ff[EY:<\FP)6+g[-bHY["X.@`MCU!_%"X3jQjAjd.9-k'VR4k;17YD/h(Xd:E;cp^fi".H<(3)&3Dr2N8XU4KR-,tJ@lM!U_$k75)rUoIcROn9BJ%q1$s7/(j\dL!;,BY:2r1UHq1)*n*"J`VkUsNbl#2/5.k[R/:P(1=2+uLpfVIFnXd[DF=GJnH53a>+'qPTK<=I54]N^1%RJs+1j^)M@-mn;tj$WG(.OD&L(<)WK[iX"S]M7ba)?U8L@J&Koj!hif,!kAG4.do(WDSVZk>=!lL9`n(H<)KN21IN,+o_rME0]i*eR[Ss_4KqL*J?t[%e2@#AV:IAbU8*qFV&nd'ir*jYZigO'@QLWdDX050*mc[mRd-s#er$HFOMZ/s^$Q8T?R!2spdm!pW/>9=?c,)borZ6R?NG(lmrh@FC.]n>)p*RU<+WEC1\4\aAF9t)5E>lt`#Y"R>!\\@pBc:t(Xh.^M-kq;`eoI@Ho#7&Lrf3EQPF4(".)hprq/+8l9%`>_c"IDC;/6(!]7SJ('_S?sOV0YK*sF2Yo`~>endstream +endobj +141 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1224 +>> +stream +Gau0BD/\/e&H;*)EBpeG)-U+)AP3N;@7coh/Sm8O"mh-JK9je<)i8Yb.PL\l4&W%j>F)^nL^b1XO5rIugqu4bi6^)`^Msa&*l3U^(L.KF'tA_=LM.*_;rrFumkX1<6U06?gFWk\"+=>r**0UDI35]0D$1b8aJMjpFMe8WoeAPe_&bs[Ks'T[sf*!k@mWnbh+!Xmdb#'Q#1'4#T\BUR1ra=IA,;PM;R[k*On>@3V%W-D(3`mES,gCW;J/naZtb$Y;H\/*D@jU"/X:c\fuYD'Kg(3I9T5oS1Ga9T@Oe![.FQB#&d>EtH(FJ)n.h'\;*QAmJ*!m@TH?'W3Mr!VF$U6Z]qL3:qegNI%LsSg*[)>&=SYOSMB))F_L2&8"q!R,X"B=t2.&Qt_cYoW^7\hee&#Hl7;ZD!0U2Ec)IJ`X1J-;Qn>NNG[LJ7*I>;("\L#ArinEYkLY:'>Qeg@eaL1j]\?0RoH_WCSo`]JU7lLR@[J7m=UZ2"_jBR"4p._A3lLB!:GLf8VZt5D6I#"c5:]4$X,qbidYHI[`hS(nJJ_O^C0[*E!;.'(L/U/o59-4l5e\`Z/Y#IolFQ^o99,Yk%!!9^>J5[o`t&ki&CH%8As]CN@h#i69R0mtZ6=8_1pNBjD\R$:?8M6n9gaDGqT1gEW>%4[:F7US#$raEj1SDND)*G&9uu,:mlA^ZnUL[XORZ!X18Y]-pArhOF\*p)OWQ!^&r9ikWD_K",6+mjEn"lme>-",F?mf:TMVf;GCj2RnIWa4WeWTGFdoeE0//&WK:KTQo*@r!C@_SW\ZVkj`Y%F$&leendstream +endobj +142 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 967 +>> +stream +Gau1.gMY_1&;KY!MReR3>!]hcK6flk3@I:ii*M[T/7u/rnU[jpUrh)0Cki-a]o$cj-*ULao4e2RB>t'2]L_k_`rP!Zpi8rVkhrRnQsCB<0oWcrT,)_OVrNA=Ofn$D>^QShZuSG8/UWksCkitJeQ,kaQ!S:VSeK.qE>esB$2Q=ohni&bs8F"Nhr&o4!e&+)5FC;C''tWM_>UGG'9U:.L+L[/1L(6tU'Xi>--f0=K-f8pGEBCh34A':=1:$aX_YPWmT=BE[>Ii%XB)k`)$e"h\R`Fu"bS7.nV"k4O]uW:T^A,SJqoU0e4hFHDCIcSn'H7@@$FltT1;]6/MBs.d,YS&*--r/W:*\I=3i?pRAn^&8@nWE60]h!dr*2g:3]h0^r/pI`7k+Y#fg@6R7E?ZjSTf]%>.YoRJXt>n&*alg!X=eeEM?[)1.6S0/Qk!S1//IkabPGGn@-"oZqY0pf!VP)SsXq&RFm^9BC.LlV1p"]oVG,2t8pYHUq/LV^YokFKOOX*35CK16+3c,bu4\:f3Ua,k[O"Zc\C"ae0k="U3(sH32jmdW\M*N1lZsJE60gsVOL6N-\e3IR4Hh>ldaT+Wir2o7`TnKqKd5U#Ep+:_G]nh+(>0d-j)D0I@/#i_9d$:hluKqMkdC>Qp=df89$AIQ>hC,?gpKjoNG$e*uU<:l\-^`rDD[t@t'g')3!L*/`^\g;3DfC/K)qSbt4>a0eF#kZSqKKccBdR/\%24FKIoH%9S5Y0d\#:F.qrJeVerf+,f`7NMCtX4?hc3()JVkendstream +endobj +143 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 532 +>> +stream +GatUphbV*C&:i[4=59Phj/=^;]]$&-+pl+-9sf#_G.P,g/J.mHm6HMZ0G9ZtHb$<:h0lnFd..]O]nHU!:*@2^^b-du!Gc&L+aJ[O,kqf#n&`'IER_VaG'dNp7I[r)\r&G?c_.ZQQn7aYZ44q5/_?6q%kjL"`7&U1eQT0i)Ya]XnNo+$pP7j;;B*5buod4@h]%Jef@iEI>'e%9A7P*DgI0"Je0NR?lGk:/&SAI#_"CW=oRW_,IOK%sEU[Apfb(qX-sk@i6LMHl;%"cUQP^qa-\er+a[Q93>B5>[Z`UF`rp/$/-UO;qie@9SGGRL('F8oqNHIT/!V3]"M:>')R`:O9QMYTi>dI\W/nUMAKZ`CEFV*uL-(6fI1IB//tlT:iL*qWX$/H(SKd%)[;Bh@1Ob6<<0e<0G0HfT;c_(WVKq/+M,+NO?qZofp8'iQidSN,U[;*q/`tW.\doJ;H]Au#Eu=#oqMd\gWR$I9f.$u9Bf$Vdr*0#%#t+1aYOIo^~>endstream +endobj +xref +0 144 +0000000000 65535 f +0000000073 00000 n +0000000145 00000 n +0000000252 00000 n +0000000364 00000 n +0000000569 00000 n +0000000652 00000 n +0000000857 00000 n +0000000962 00000 n +0000001167 00000 n +0000001372 00000 n +0000001578 00000 n +0000001784 00000 n +0000001990 00000 n +0000002196 00000 n +0000002402 00000 n +0000002608 00000 n +0000002814 00000 n +0000002892 00000 n +0000003098 00000 n +0000003304 00000 n +0000003510 00000 n +0000003716 00000 n +0000003922 00000 n +0000004128 00000 n +0000004334 00000 n +0000004540 00000 n +0000004746 00000 n +0000004952 00000 n +0000005158 00000 n +0000005364 00000 n +0000005571 00000 n +0000005778 00000 n +0000005985 00000 n +0000006192 00000 n +0000006399 00000 n +0000006606 00000 n +0000006813 00000 n +0000007020 00000 n +0000007227 00000 n +0000007434 00000 n +0000007641 00000 n +0000007848 00000 n +0000008055 00000 n +0000008262 00000 n +0000008469 00000 n +0000008676 00000 n +0000008883 00000 n +0000009090 00000 n +0000009297 00000 n +0000009504 00000 n +0000009711 00000 n +0000009918 00000 n +0000010125 00000 n +0000010332 00000 n +0000010539 00000 n +0000010746 00000 n +0000010953 00000 n +0000011160 00000 n +0000011367 00000 n +0000011574 00000 n +0000011781 00000 n +0000011988 00000 n +0000012195 00000 n +0000012402 00000 n +0000012609 00000 n +0000012816 00000 n +0000013023 00000 n +0000013230 00000 n +0000013437 00000 n +0000013644 00000 n +0000013851 00000 n +0000014058 00000 n +0000014265 00000 n +0000014472 00000 n +0000014542 00000 n +0000014826 00000 n +0000015364 00000 n +0000016065 00000 n +0000017533 00000 n +0000018700 00000 n +0000019907 00000 n +0000020202 00000 n +0000021704 00000 n +0000022771 00000 n +0000024052 00000 n +0000024968 00000 n +0000026461 00000 n +0000027850 00000 n +0000029008 00000 n +0000029396 00000 n +0000030857 00000 n +0000032080 00000 n +0000033219 00000 n +0000034700 00000 n +0000036178 00000 n +0000037377 00000 n +0000037760 00000 n +0000039082 00000 n +0000040276 00000 n +0000041529 00000 n +0000042876 00000 n +0000044113 00000 n +0000045075 00000 n +0000046514 00000 n +0000047823 00000 n +0000049033 00000 n +0000049547 00000 n +0000050974 00000 n +0000052161 00000 n +0000053361 00000 n +0000053860 00000 n +0000055319 00000 n +0000056424 00000 n +0000057423 00000 n +0000058386 00000 n +0000058877 00000 n +0000060372 00000 n +0000061546 00000 n +0000062587 00000 n +0000063559 00000 n +0000064435 00000 n +0000065920 00000 n +0000067147 00000 n +0000068251 00000 n +0000068760 00000 n +0000070053 00000 n +0000071371 00000 n +0000072460 00000 n +0000073126 00000 n +0000073504 00000 n +0000074833 00000 n +0000076080 00000 n +0000077337 00000 n +0000078173 00000 n +0000079195 00000 n +0000080262 00000 n +0000081421 00000 n +0000082424 00000 n +0000083476 00000 n +0000084100 00000 n +0000085497 00000 n +0000086814 00000 n +0000087873 00000 n +trailer +<< +/ID +[<1268d636531d94991645c9689615c6de><1268d636531d94991645c9689615c6de>] +% ReportLab generated PDF document -- digest (http://www.reportlab.com) + +/Info 75 0 R +/Root 74 0 R +/Size 144 +>> +startxref +88497 +%%EOF diff --git a/ai-analysis-reports/repo_analysis_e395ea2c-ea3b-43e0-94af-c95b2815aac1_20251110_055924_analysis.pdf b/ai-analysis-reports/repo_analysis_e395ea2c-ea3b-43e0-94af-c95b2815aac1_20251110_055924_analysis.pdf new file mode 100644 index 0000000..a6d9d50 --- /dev/null +++ b/ai-analysis-reports/repo_analysis_e395ea2c-ea3b-43e0-94af-c95b2815aac1_20251110_055924_analysis.pdf @@ -0,0 +1,1251 @@ +%PDF-1.4 +%“Œ‹ž ReportLab Generated PDF document http://www.reportlab.com +1 0 obj +<< +/F1 2 0 R /F2 3 0 R /F3 5 0 R /F4 7 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 71 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +5 0 obj +<< +/BaseFont /ZapfDingbats /Name /F3 /Subtype /Type1 /Type /Font +>> +endobj +6 0 obj +<< +/Contents 72 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +7 0 obj +<< +/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F4 /Subtype /Type1 /Type /Font +>> +endobj +8 0 obj +<< +/Contents 73 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +9 0 obj +<< +/Contents 74 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +10 0 obj +<< +/Contents 75 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +11 0 obj +<< +/Contents 76 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +12 0 obj +<< +/Contents 77 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +13 0 obj +<< +/Contents 78 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +14 0 obj +<< +/Contents 79 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +15 0 obj +<< +/Contents 80 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +16 0 obj +<< +/Contents 81 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +17 0 obj +<< +/Contents 82 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +18 0 obj +<< +/Contents 83 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +19 0 obj +<< +/Contents 84 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +20 0 obj +<< +/Contents 85 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +21 0 obj +<< +/Contents 86 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +22 0 obj +<< +/Contents 87 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +23 0 obj +<< +/Contents 88 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +24 0 obj +<< +/Contents 89 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +25 0 obj +<< +/Contents 90 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +26 0 obj +<< +/Contents 91 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +27 0 obj +<< +/Contents 92 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +28 0 obj +<< +/Contents 93 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +29 0 obj +<< +/Contents 94 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +30 0 obj +<< +/Contents 95 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +31 0 obj +<< +/Contents 96 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +32 0 obj +<< +/Contents 97 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +33 0 obj +<< +/Contents 98 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +34 0 obj +<< +/Contents 99 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +35 0 obj +<< +/Contents 100 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +36 0 obj +<< +/Contents 101 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +37 0 obj +<< +/Contents 102 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +38 0 obj +<< +/Contents 103 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +39 0 obj +<< +/Contents 104 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +40 0 obj +<< +/Contents 105 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +41 0 obj +<< +/Contents 106 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +42 0 obj +<< +/Contents 107 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +43 0 obj +<< +/Contents 108 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +44 0 obj +<< +/Contents 109 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +45 0 obj +<< +/Contents 110 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +46 0 obj +<< +/Contents 111 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +47 0 obj +<< +/Contents 112 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +48 0 obj +<< +/Contents 113 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +49 0 obj +<< +/Contents 114 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +50 0 obj +<< +/Contents 115 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +51 0 obj +<< +/Contents 116 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +52 0 obj +<< +/Contents 117 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +53 0 obj +<< +/Contents 118 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +54 0 obj +<< +/Contents 119 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +55 0 obj +<< +/Contents 120 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +56 0 obj +<< +/Contents 121 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +57 0 obj +<< +/Contents 122 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +58 0 obj +<< +/Contents 123 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +59 0 obj +<< +/Contents 124 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +60 0 obj +<< +/Contents 125 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +61 0 obj +<< +/Contents 126 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +62 0 obj +<< +/Contents 127 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +63 0 obj +<< +/Contents 128 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +64 0 obj +<< +/Contents 129 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +65 0 obj +<< +/Contents 130 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +66 0 obj +<< +/Contents 131 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +67 0 obj +<< +/Contents 132 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 70 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +68 0 obj +<< +/PageMode /UseNone /Pages 70 0 R /Type /Catalog +>> +endobj +69 0 obj +<< +/Author (\(anonymous\)) /CreationDate (D:20251110060557+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20251110060557+00'00') /Producer (ReportLab PDF Library - www.reportlab.com) + /Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False +>> +endobj +70 0 obj +<< +/Count 62 /Kids [ 4 0 R 6 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 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 37 0 R 38 0 R 39 0 R 40 0 R 41 0 R 42 0 R 43 0 R 44 0 R 45 0 R + 46 0 R 47 0 R 48 0 R 49 0 R 50 0 R 51 0 R 52 0 R 53 0 R 54 0 R 55 0 R + 56 0 R 57 0 R 58 0 R 59 0 R 60 0 R 61 0 R 62 0 R 63 0 R 64 0 R 65 0 R + 66 0 R 67 0 R ] /Type /Pages +>> +endobj +71 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 610 +>> +stream +GatUogMY_1&;KY!MRfa#.Y7J/gM:3Yg8Bn/4Y0^W)i!QZef20kmn(Jq!#o$B-,MCMjpttN^cQFn\fKQVCi*+!q3s=Ue*s$fA#<@<&G(+OP?Pu'-h_^?BVBZEW=.\DN*`hFM/7`h"""]UV@`Aj!_%P:t$kdf5UuY7G>f5G%N/3.6#^niOo'&4LIJo6l%*e/LMDqNJ??Q-V;t1>rFBlt%dJN^C%Aj:G0n";C2)C6[Ui_e0,C@.k)f.GNpdR$@UD8Z=Y+/I6D7A5cPB82Hf@ArSlVn_aUP$bI1iZfjAWIiRf3i]PVXp'O\M5T1&'"GF766h\#Q<8lqtendstream +endobj +72 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1274 +>> +stream +GauHK9lJcG&A@7.%!lq1BPL[nXQ5Zc%N9/iBk+R'!kH\-f]$'[?':*2rq[X-A-%;Q5A3]Xg/=q>:lRruh9`8,GQlG/R_"2e#`V,k+YGZ0*@G^508tGEf,e4FF:6j0-Ns-ghNmV3K3*cX=&e%OJbVYt.uJ0pBp?ta'c%T>OSq/L'=?U^UAe8dL=s4Xe%(MVH!l"dQD@JqW%("6Dgm&)iJq3`rATp.8(t3`>VtqH::g]UaVT2Vs@l`@EL41EV2[+h+F/jXcp+qU46W1'-*n'\H3M2l$[;P+)>L)K3#MkiocWNa9Y:-(\"B]eg&4jc^6EG/%+Rtk"Bg<6DR9((E@NiWk(RZAr4'6#V&%5,&=rbO(&q=Yn$PY2BRm$cDAPcUY7Y9_cWCGZA/V^QcTC(;plqp:C7PK_cb79H2*P7o@I+eq\"s0%41UC&E?>Xk_Nm2Lh"hhUXY!B>21EQ$cXW6ccK>lekOYImTNgS2ShHAZ:K92f:Hhgdj*sUu31p&F&e#E^F_[Mctb/Nod3;Wl`%i.;W+TEP"'f6`'TOK>>F1"m3ChEJo]i=_uerf\Ut9)%,)uk#-2;+_3D'HUf$dRAG_Y@-BrsqEn8>HOu@&s.ZNNQtmYLia2Z#iP(?PQLmI:)sX)F[+n?=1Fendstream +endobj +73 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1262 +>> +stream +Gatm:gMZ%0&:Ml+bf_2*O_?%D(X#&cS0m&/BM`WKDI3ehN&7i.8>Jo,hKXtXq[R2X\$`kc2_blr)KFp/S23NYi6oZHs(<(^FUISeot^/B/\haa!7Kc_2Egs'bZhh"G9HThVpe3u["8?s*DhqU>3g%Rq&uQa.[O6"nnk8ToiuQh>!o)cd"VKjrc7H"Q1Gm;%agQ)(T%1O=(e!U18n5=#[8JnH'W\HF7"?oa\op/n$kl'.*=O9KS*9ETp)4\5fuouI89(&41(c'3UmbWnH+$c$..'sa'fjgIK&ohla1SC^8!cU[iu;riWOM@MsMdf(kGhGNi][G_iV6r[ME:n*@9fnFd@US=Zai'ajE5ALb)TdlSBH2,)1Kcs%*M..e?n;?F5TLD-R_28u%ZK1A7QeX--$#51Xa%:Ma]9039,+F\+/*&m=SJlcd`RYDs2LL'0>TrF4Cs#>sg-\$>b,+7s'Wa!&'JW:o`_bab+Efk#Ne/)f5BQ.X'6PK`%Spma=ERr`KcS)W5UpDn<]g^LL`1:/_=tQbirOAE"!]IZpXpK>@o-%chJd4&r]%Ba]]Y]G)V'FPGa_^crt-NkaD+c8M`'-M;Y@+!GC20BVBAPmc;f:QC6Pt!*AZF?un,hMLQfJ?>K3hC,LG/ju1Zt1CUE$Z>9lc$jBR7m_7L-$=].g=bfU8X7TiR*Yl*9f%X=D5(%(kLC[qhcYRk<\UC`(JtTb#Y.6%7iIoib-VcSbHkQi+6T"Y6\$=_=+Bh8@!dlSZ.Hu/?4o_Q=_f,EH!50e$h#D+M3Z4]fVTZ6;o9B<(_/[2[_II(/A(2E<]7\00:N$FGL_i']se%n$g5Ip)rnD1@W3b.9_c,V-6kR(GD-.bu6dlQ*;p(mV5pIcYM;$(ntZopB`*UNOY>hE,8=Eb]P%$JVlmqtI1HLO/GS[5%N]cum?rB*#PK]fp.+ZT?+Vo'Xf~>endstream +endobj +74 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1130 +>> +stream +Gatn%?#SIU'Sc)J/'^isVa+dTqK2/ZHu'f9e4is0@NJ?85.3IEU.8'Pe"Qei8!<1/?H&U7D'4X&Rd\/cnWF;cqQf7%RKL2o@!^(JECa-GI)h$'#-O,Q^JEL2Ae[Vnc^Ztd`UjNVE#=L"56?TF0"l>C393p:H@8,,3epM[Z@sd+U'EdYA4$`;ClJ?uX.:_[$$0M`C>u`GAM#\5KjGfhm2"01-tJ`MJ\p(WTXX_L_jj-hM'<^fl_eg)U`]OL8U+HMP_P?EM&DU*oM]XmG=ND'F*b[$/JaG+6EH[DgJif#R2lfl]E@f`G&3Z;\L@bf)(+.4L01XY)rW*uW^=.r$.iAApo*i9MWi)JLjnQO9KVlUY*!1cfCckJ$SB(36gdib48V#6&D,jIHkT$AZpXWX<#Wtr>-.=oX44pLhs_sXT,W0n%,)6:CE@g90!R7=B^>SW5AaKqX)&[BiPls/1+p4YI]OJ;n,ZC9!0\(^LZV9AG\VF-8^BZ;`4:=Vph4PAGa$8qc3KW?[IXoa$*mdfH1W$h[U+#pb4PQrB5pP^6G&(JfK:/F%8E(X"3*#LFBj'G:7sR(SskWC#Ft?Z)'50Ze8a!clJm+7t(P!17+l`jfMc?1OKlTXcd!Q7e0GTGKBZ=\d]9(dTZE_6(Y7Z2tA"#LEb&WdEehRpQZl]Xm=mGl%gS:['W^#Gd*id0H1\*6lD,Lmk$%N'@%c*;L$S+WF+b5F*8qS/_NJ);[##[.=+q)-4<9X+/UNcQnS"-pk>%,SKNQ#q&.Ap.0\reL.B1a[7&]Xib8!Fl+_[B!("9P;+0K3-4i%mGNM8q$F@"Tn6dY)ZR.V.B]H2+oK/`%@S5ij=u9[!`-Xl9>1.-?]02>;Uq3u2XB0b0OtM-(16N_[W]KKV1@.hVnh4YT0_5~>endstream +endobj +75 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1204 +>> +stream +Gau1.gJZcs&;KY!MVbBn>FZtnf^W8:ROr^9QsacO_p'=3V$V2SF+Er*0"A`(`n^X+3*pUh)K:iQu\7Z9qI$],_\i6))n8/+]*)3RqCjT:QS?0nnS.??,?H^m-;dim-B#>^-VI>&Go.nkCRDtVdQeGUth/'0F`V(BDlJp4f''fmGITpdcbV:4.+p#\```AJQ'l`_,UdMVRf%f',;GJ\5mRJ+4+!@>D,_\D4)2?g>4GRI_=LfXcnt)Ptm)f]m9'OZB?`6m&dJ=**C=AK3Rd7$5L(.3QRTik+.h1<]Lh&"h?q]&5)>L6jEp;?DE4N2Sdi-q\&RBcRm,m#?J(_Kc4rB*E.tb3"6$u0%$>4i0\ZZ4AuF&](=^MUp'//A>K7N!a87K][;06.Ck>j8"!3Io!.P)7F<@p)XT`DGh;"fp!PuWK2CJh$=*mfhdS:1%/[0gmO,c,ZQ5Db'WZ)Ih'AHRnV>_me%8@E,ImBCeeTY&EP9@olpJb8lJTrD_&]Y$W,K$'<8CY#_ssZu,#^(/JcCkn">9Id==VKe[I1.SQ[-ZMJat.pSH\F4MIeWDTBM8*;P,m:\7,Ks8R!F?=2r@?35fpj0W#$FdQ3?#=Ja8U$-]QiY*LF%&mqO@.]GfNYX&;-8`E3Z8'>#N4+&d'oZa;6f(2bOJNc0K9j1B]894nTs:#d^r*p;iGQJT0[_n(M1oHK=D:X*#m3cGYR('Efgr`[E>CI*NK)@SE_cp<9Jku@6l:CW<1Y'2ckpVoC>C;J#_s!'>*\.]7Qqd&\[M#WQOI2GZJ'+LJ$aO.'b?eF,qWo-/8K@_d9NGF_EE<9endstream +endobj +76 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1011 +>> +stream +Gatm:9lo#B&A@Zc[\ouC@P!#HXj'A*)43;q++&fQ(0eV@*(`EZYHMIL#*R\N6H1f&UQtPdh0n"2J864S]X%t'HO>Bu!lT_5"se-h8B5\3p)\P@BkNr$*"jVjL5@p!$uTR:H-`!,>B^4uC?(1.Xuu%6j:@)WpasI,O_L1Z:32Zb$`*NYI^Ll]k1PO\L;uQMit>!^=3*7p81PN%SK5fRG`%Z-.SA=V)n^Q@CQJgBaP?*mtLcfP89,'6-MS19EA-icm@^4:s_*4`dj+-8e&0B\YN5:dK1pBkB<%*%,sH]&ksM2VqF!r;mB"inMubr]5iJ1A6^XP#mlj"XfHfTY7HRB9t;n.5c:u,HtWO`6$[@_,sElI6sCGg^m4o#TO`-!;VY_\1hR[X%Bsh#NG*-3%BYnPMAe[C_qfp0;c*(nas=*+&<&uqpd1b@_i![[rh:*=a\\G$q?2fP_54o=0E/>kT>n=&Gu7ef_Uq"&T4CkUYPFVEDaWJW<40XG\RJ4=f>K!LIP"DL)6QGJ[[[7r<_cPg.gLu46>A9hF=Y#K^g\JFVNDpJ(iKe:5M>O:p;6<^d&R6lH+A^=[ao6`e40NQ6*pshMk`SqFY"brp/Eb>ie>O&/&!)98Z)UIGR:(WX&CXB"iZEgFe,e1I@#aD\\PPUR[[Jd/>AhVjJ43l!l(!P%C2)o)55H'(Lu3]?l;LQuQ*6rS)3FJuBd0IMX)EGdsAgJ54=i0+k?'Zt+Il2joa320%UVEB@U(j;;"R8kXQeJW-/h&EHU@HZscpnZ!1SSPWX3%+J(=(T5S.]jqT/n5+3gid"rVM>pFNhmmtY)7Bm(gF;%!N'qY9ZcI&Bq8#R`-I/Z#qc(U2g0X(F*B)W2UN`#J/@&llO5SUgj#YFDUL)"Bj&:>ImaZ1SPtB)6@$NpS.-"(9^4!gendstream +endobj +77 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1016 +>> +stream +Gatn%Df=>`&Bi7L].-uGL&8P>l9cM0.R%:%A@u;J9Waf!stItL=bpCGk?#ERU8N=7"+(5nK>gd(iZO+RN19iZJ#dI.-dqiC+IVJFSnCu#k#3o8YNIfQ=hQ)qWn?>:GD]UF`-`]WT=T#B\da31F\uiJkgR,KkV9CUFp#b-,gNLJ;YikXAT//?ochUU1)jGF@)`^/M]$]-;tBf0"@XqSsdtD?08X3+S&S6gS#`l@rhp4_B8p#laur9$00BjqQF;_)V2h*WOo*\oc3aZkt.k:9#EE=!t1tG9*C9.6sXt^sD<*HQ`ZpF&dH7X`DnU/O/s?kWY$*k"0V^4"nn;I&)arb^^/Q?>B]Mh+dse[f6QIDg^P,l*]iM9Rnr*Qg-gKC_ZS\.W:?YtcUOj0t#kMuC=o+mG0$g9U,5l1os,eZlI72IiKV,fB4+RNB/QJn=/:mmeFU3g$5$PuaYpKV[1(3i4_H0/OZq)aA)4+`"FY,;ae..`nX=%=qgk2T:E9V1pH(lHWV*5KPWc3&Xk,Mq:8DjtbsVD]h)s5tbo"^QlV&'rZC7k;dLie:iIbn?8)X[dGu`)ZqDMCfCMJBc#%'Vh`cgGONrB&n(c[3~>endstream +endobj +78 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 991 +>> +stream +Gatm:>u03/'Rf.Ggm8>7THn7*KLpMu'hFOs!MaRCWf2"gHLjY3*Cfkt]2$O>TFFF8O]j,UmQ&!F51_MWFN=&-M!*LC1NVt2Mc^*I[JS#e.L4O[bRTqj+hRf5V5>lFf6Pgg:<0K%$(">Bo[_p)@P%IUsLD9jO=r?\Qp'PJapd2HVSbu4#uYZI8gorNLF(ATm#E"6(5))pf>Tel[jCCpHj]uU_tXl(SI.-,q@EY%4IphcY.nuXtlc!j%9JblYK8ACWLA/kC8nA)mDotIk7s2t0AoAW\KJ>_[$#eMX[2tAB-5DM:5r8tHS!I0ASZ@S7$+TsV2tpc'YNR.i"VQ"khJRoU67LH?BVtV=[$3\I_)Gf"*CZV-WA#6bmlqTY==#O[MAY,.4.?4&nt&gr:]!j@J/3=gi&0p,F2N%4DJTdTGq?>0JABV=E@J&?fO#`rGFa"SWMmO,#*hKaX]s"RJ]ELc7#g,G9dR'_!H/"(9eH$hujg+P#1U]tH,9R,X*L=hC:bq-U;/JOCf,n9kn->DU\8UJ4A>96S%!VnC@27T*I*rn!?PAhqZGQ&RZ*>Y3@@0'oCgbV>-HdAR6P9i[f-A)jNP"~>endstream +endobj +79 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1426 +>> +stream +Gau0Cmr-r=&H/38iauSK4(PMe_mY_HM3k<`Z>R)#!6ik;sNI.%cb&3iJPT2ksT.D3>&+:'.8+c$@XUfVMt*rU9sXRDZ)0Sr;P,WSHc$!\<*$bA=7B7gHDV%mk_6BF$.-#TLWiaSA%rd#+&.@eY\mq+"TJ"0l]g&&+V:Q*oc%Aa"jqMU:,!C+1nKCOSmK-6P?is?.2d1CdHN0A6@)0oW.D+4tnBFqtUrkJ,3,tZ:TE"8)_\]I!Z"gNQFeNWW0;1YqXIIR4J,IB<(?gh71(XbgcIbjjn=^J#/gKBWiD=lT0Y8C8ZdiVYiM+99FbIUMpd2VGK8V_bgr2`f:aI%P=j`2MLf3)X4i*P"d/]jZ!9l5T9_kN-4=F7ntmIf4&ZYrXHss0r.EhC9H'8%3VB'cSV6l+l!F[Z#u#-:):j%@M++FLnKmm;>L>NM9;oA"LqCs:3GRf7KWT7%1gXRRi??#!gaqEAh(isWlPb1m%,>2[rN?$IWf1Xc$ESi'=@\aebi8?qqMcYBEir:P\\H>PA3[(>:Rn4i%aO@k1oUu\CDdEfU2qqZ;*u.R$02b4aD$+J`D.b]Z/-:Y&rbT$.H#6G+R#%ilM[a?aRr`??&9<'*@aO=7/\8Y*3j(@Le\TSQ%!K$42Bpik_B9rd5rX\(]6q&U[b$Ma"!Iar,A/S*N#3`m+YoAg,d9lkD'fa<;D2oaW*A1@1`(2#7\4pR$c:W`jGsQid1\<\HgsBSWZB>1ppY2WB7?aE'$c<*CXj):*BTR%b]9eY.5bs0"rJ.n2;S\;1peph>`hbotCnj\4,"@p!U@"5.\;Bu=OgYF[W7B\5>rKuoq#rS8O(E4M3*TKg>aiP8ORbhAh5:V>AalsKs"pYj"7p!==pVA"VBk8#Z_V<'!0\`51H,ufk>2>j;I.]lY$XKnLQe!*?3,J:V*amlJjuS+iU,s;.@R@K>q$5/U45fiSc^d3PWc"@8p1;U3cYk79N"HM(0<'R+*I4[c;hZ5s2"QbgKlHg&2#J6Q!n>AP_@A@aFdNLMiD_.s9C'AO2:+rc9D3OL]\3?WV_Sp.=LT:Ehls(0^Y)f9n(ZC\WCeHeVL+Yfb("/C`/Z'%KE:cAHhG!_~>endstream +endobj +80 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1092 +>> +stream +Gatm:hf%7-&:Vr4EI/P\%#lIYqW7&m8J4^A*DEc8Ja\F@jV@0_BkPM=sojNKgRu$lG:%h0n2:G97]Q:S5FNk7td4#3WGr":VR^"JHVuFq6Obaet>1^GAG)REiH%SYbr_/bj3LY>4<;TFD]2+ffdk!Z-bPOj7mC^#t57L$\93[S>[P=fX$^(D8CZ"+iolJ3tJ\_X\='$Ce4rJju`Jg^5@D.R7]_NF464m.qG6bj"X4E*9\%fe5%qj\k.@V$47mmG9ZarO5B]s.E/[.goO`#>9%V]j-;6ON6'0#n!Zm8IqTk)KQmL-]4T1RX\5SE_fjjdR(A1_diu$ch((\6=P46G*ZW;-r7neg`8K`B6OHNoXA$pQ[/ZJ!:aL*c4VAr=6&YfUVFps>?()>Fs0^%#]QKDX5f@3#3f6^-la&ST;m]'!<'oCi4IPeC'*g?D391dE8CXTA%Ug2H0C0sROVXZB4n+dW3lKe=Q9AKk&,@8UJdFI2R)#^C)\CLeY);HkiHn/_NjN=A;`J"r)k0sT:4g-]eoq*Xf8f$f(3sM.l(0VW5r7pm3&cb.N3%p::0`eqX"2q2ZiL&Y+(fcKT!t3oE$(u.Zqn]4.E[k'Er>uLS&<HaW7-NssbbW%?3`^_3DVo=o(phI0/jU=7:/*-EqNLYnNb@VnZ#/k(M5i1gjhR3*G2EdP$q8]<2dic=!OMR]rCM6JHr0n?12a9iF*DAs`"S:P@CSfuK\n@;(l>JjYK/addQd,lT/;aCn,K-[oY!2:[fpjigq>@3\fR+5&>`R4%O)>6!M'&q&%VZ5~>endstream +endobj +81 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1149 +>> +stream +Gau0BgN):C&:N^lqIO.pW>3jS)rujp3(jum'20l/Ib7f&&X,X.4>ZO\8bV&j)Jj4B;CY+?<.RH`9V:QjqVm'!+4#B`3PbMPFKRaq(sEfSq2Q!W$&n#Kqj-ud15[4`,KGdj1$rYfR4;GU.d+fZ3ou7.>e=WSJ^KL*Uc@#SSm=4d-c!`L.RNj-rWfJ>+MJM>F>Eko,j[>-tQjU+[Hj895R-j($Ymfkt9%`Y+9UNn(tog]B'#^D>)fe*r&@S66+0kBqG%r]XMFRM?L?$X@suWI#!BEA9leHb5HlYJ5q_7>W_i^iNb.HZ+2K_0SL_'[i90@=aefU)^jS_^cC=A!ll;q3\A4&.p;(t*RmpK'4ic))WWth0^;P.oO6MNU/?8XC-4J`(K?0@2Pi)Uc&uB`S_EYC%;@&i]g@)4F6i)Do+O6ds7Y*o58G*$Y[nn?Fn?H'mVkME8[`TqRnP@;J=K7B_E,g+_;]KE>^=r'WCUjnP!Z6;#[5,H,pjZ1G>GCM<_+.^``T'NnHoN%;kgr*$$P9piS\3UFLUDSBI6\^@c.,k]1]Vh.2o7e1ft!bsHg%Fo`2RDjk<`o"9\XaUendstream +endobj +82 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 717 +>> +stream +Gatn$gN&c;&;KZF'RQ6i`Y`]M]@=6dSk3NlRnMX8+_*B[*(_u,T=uh3&'4r8/KN*o"0D4NIt'$QCgm;j$42lp]r2ZuMU>^1F&fU[kpJjl%G+fF2a5X&^I@JsE/Tf3n3X?!ho>\t+:=Xc8'\RifqtS0_Eg1C;+tu].AG=^AgMh5h_ctN*.MWFeIYmq#CU8OkZsi%(&L8OZ`K_ASSn1l8thXMaXL.B'@jg-03fS3pr\NN+Hu8Q8NantC9m7_S&A<:AC4Z3$*$=sDd7!WlTka,*lG3o[#nHAg`QKW^d;X%KkX[RR$^9i%Ip4gIsGVE.-e\7*$)`200(s4^SODq6,32ghi$[0@jG9`D+;pUO>V?>1;1-_I):WK>V[Z%d^[a_#:NnhV-(4!*S8%u(IXS!TKY'U!-1*/4I+-l`=CY;$t;PM:j='U\5I4+KA\G;"Xc<0W$XHi8uorn.m)V0RbS3SL3KVm4*d]+n3kK/DV9?q/W+6#tB(^Ygb=-/_I%G\6;^=GDr-B]l-NK9GZ4AlL_Q_UG2D2^Ar/0]J-9DB/BC_nBa6h_C_[/M!tF@82Gd$&"U/`Q'kr:7`Aan&3CPUendstream +endobj +83 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1411 +>> +stream +Gau1.;01_T'SYH9/+1U+YapNf`aI_A6^-1sPAV>pG1t&rO=s^o/Q#)ocsPZ\)$6UlR=R'9,f+Z"UT)^IYXWIL\hrq'X31O[I(GWT,+bsj2ir+r'rmF,?fms@Do5L8*I)W-f:J=BHtWK:'R99gEE&lW4A%\q_"E6[ODCIJK!=4(r(Fo1RTf$d/F+_RGpi:Mu[RWC2N@),bscg_1KYU^9*U7&9tXB&4H[+bru$LRKCX7-AZrcNd\jnW6=i4ibp-jr!<;M'2#Z?<[\9h+JtMiJ#lph'H^RnfX^K(dD\92OaZ!?pEsB]ot0'@Q--+],NP\EB(/.rD/jpm4*oEUHH/dCX#F3?["m&I>*o1>c+!:mF':0f:di"Y$bd`Yo`@W9?kBb1*F$CQL,D",eoaheP62OnEeIjjp5f0:6Cj+!bJZ6Bmj,;#Oc^2^0e#fT8G*iI,fN-B3s[7[kdX"QIXSkD[X)?9k8EWJHJ>1;.%)i]H=n^l'GRNAdorU6S*QnA\",sdZDgX#,P',QrHi`0FL-Ke6f.8Tgk7#g(uW=6o+9[QhTd!O!$a4`Jl#o%l++WYm#72bG/mQi]HVs1UXk2FgNO]M[hKDX+dGqe/grA&JWD:'7TWf_i4L;/4$U8W+X*74U]Gk:,cI:'E2!R*GPjN&lH$7KAh'^i\BZAE[Cc_,EH@a;JugX.s(:=e,8qpd-_3S/%...tXbcSqGYO.SaV.7g/S-):j-o%n,ita]Q*+MchOARXSZ3$d\9i6H=4']nrY5"i\WbBodb`9iH'QrE9O%^#D`hr+_:=#[\SZc~>endstream +endobj +84 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1149 +>> +stream +Gatm:>E@K#&:N_CbY'8YWCu:,iJgM-FerB/KsXYQiXH"",S%4&.f/Hm$(!!C@GA(S6@uIjNb?Y-0a!=tXSe)WJ3a>,5;HV)6uuaRa+`^e5")OA/[f"(KFiCa:)J.AgT`oGY^JL+`Op\qeUD$p!)oXh0$FqS#\d,kBN,o_,8:Q,.H6KlBT2PGg7J0*NTB\TC>eH&SSL1q'lnplm,e@^J4aA[Q<_]J+W$+Hs,(4"Xs#dA%49c>aPg;Gj]79K6CLJSW5->kY'>gg1:;G)I!SPOCAgAYLUFaN%4j'(_NMfea"*;qoMrO<#%NiIKF$UL+c6F!OsaHcoHe@#?*B*Of4Bpbhl]fD0GaG$!$0h#keE-"qKCe&\:_EZn+$rGm[lFl[.21*`ie2ibB@7:Uj^aGFfL:3]>bnNKJlFbjb@l(04L46gJ-;JI$(+6?ULRi6U_u96TtI$_mlC_%3de@FmEL`!/'$JSKPJ4.BV\UZ56i"r*_r82^:c9-Xg-MZ\tK:IgtT^hkHk1:A6qE\EchH(Ju^J3XIfPn$W"#KSGDh9$`.X%;KkM>PB%l>l??aG!H-i(S?N`><$lGS1>Br(+=3.id@WSsFSl*9>%jED.\*.e;H*,c7VP7[/4p\kg7"2qTiiOum\]L+^<]MKGkC%dmFgbX)NUQcW0ak44,_BG\CG^!1E1cb?he)H?o$pV@@\\gY#Md`1.G907aftBba8MZ`hs]WqJ2n:e_C@<,?"('`@;:MMK<365]<;1We797>D^)Nb4nA:#KK'`YU=rTieB[;gA[1tlqZ97hDq0Wm$tDDh&*GgA*EU*4%XIEF"5W+JT^O8^17\sl8DPJ]V\M-?6\n=FU[HPPM7(WEp/ntc;9Gendstream +endobj +85 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1247 +>> +stream +GatU2>ArL\'Ro4H*,/B/Y%T^g+I?j;J[Vr?'qMWeUNo-lDD8&f\PU=\ebSlbS-t5u@FZetLtLn]q=m&/#lt^SV#1Z8J;XDV^Eu2s#hD7q`Xm_N9S2+oO49Ju#L:[TL7[k-8+0t8fK'ZPQ'/7]<7%4>)N(JKYo4ttInW@$9/->V4;H^/4;GR0"_Jt5A3!l1,tk)-1&m+1a+\oX7ikRMZP4IJGi^)1L#<1=^Hk)CBqFcS./V`^Htro\=g9h8QTXAM\QncJ(9[rSB'GaSMc8G0IFQ"$j%V.uBY'(hJ3W#]mm3$uZh09T1h'qA?_"R:#qFU`jp@BNh]*`9e^L8MACN^PE74arc*g^X>H'IukA>aaBN:gRe`&.>iIT/V9@k_0kp)M"ndn]i[fK=d9J$">4o'`K3`3.3Z^5Q('[2%`1XbQ\4_!*iQ<4Ar!go9G7hta<+^)T!n&XLns$hn3'W2!ai1`Y!Si.;^)t/bnbh\Q-M.'3>Ygms_;^>J[oX+H)EqYWNBfRZS$SH5$^Mpn>X&>!H,>Z771ON8$6J?`p(bnnnZfn#Pt=rZ7K7hSdX]:R:Ps@uIEjM]NR&kG(lY^u,t8?KCO)KkiRl3s>soV-DWdCE:80M)jd=OuXP[;>RR7(._OPucImpo66&$$C%X2,/CS4K*D)^>=7OCMM.VrtA`tcpD3Yfta3YfuLm2\)'KXf=S"iD7H\7R^lA%A?:HIgTME3*-Y]qe-RLo'smQmc*l,_:K'LroQC6]'\(d'?/rohk:k`3%LaC?hWW:(\>J"heU6V*^FVG91H^JG5dAuVR\-sJ!.QZQ0e2@0b/A+]f[h7BIJ1H(2@paOC4AcD/Wm5Mg/N3`:LHBT-W'tYGqUYcfcjPRq')Tm\gfNaPOc+E>`YBV8RtgBin*5UUY.2RH2WBhS**B?CIXg:&e/TrqQ9Y>aendstream +endobj +86 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 284 +>> +stream +Gat=dYti1j&;KpA`BOD:-.eqPajubW$nP59/B)UlWu@mY*!C2@'2Tu9FGN]_o&"lQSI(uC)IM"p.uB#jJ``%?.%\D>0?@^D$L"Zl,S1?P,tXR##p4h1@&q)hdM_3KbqCOd;=ag1o1hV[h)7d(CMc-3eC\Nd>B\n+p'&'4a?AV?TR?G*?Q5hSm:A[pi^S4V\CilJ+>:ph\dZu7N:(B4\F/'/O6L,JEk).B^\l=oT"1l)>!t3p,.4E[X5*ePqSV8d(VOkERLEE'%$s.5Qf!e\#<7<`6i~>endstream +endobj +87 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1386 +>> +stream +Gb!#[9iL(A&A@Zc\pT?+D7iF=jou<"98-)W5e1kPUp`+b1ulf=lm9D1D^69<5ha.A-06U?,^K7Zldr=E!M/-^<'TDpkWGT#T)o?dMWPT]H39&-kTW(ao-i6R3M+TB0^VR]dg)nd@2Ns9+ZKu4R/l]!6dE',D0*oX>2kPV9ofca+mch8`1qn52l/=a3`QKcW3R<0F\ddSt[Io,d4O8U"]qshSo:Ag=@`m\lm*=f%O"#a!X7i::pNN)n#bW%KI&ph/s\MIUKG*SP%nlfFf)7(BL=kM"H@)Mdf\"s_Y8/OX@tE_gS9PAmt^,A(umLkO(hW5_ogAd'+W!mOp_I1WR5L\d[H*%F;Yg)mNhQN`;k%Y9r<&Bl*ag;fP&H?-"FBma5f#78Dq]6g_P*Xa_$R=kP$*%.K%.VCJH74(:d'%>1s-cnVZp:g.:n)KH!AX742n:"Z];LOX\Oi=b);daMV_e#_o*A7I\Y_Qc2k@Nm8)l7AAD6<>!-bJXp,RGJeE7I+`.9`GCcUip]A+5^nGDJ*Y6c8IZLB^/ZW-]S8,Ng+6F%uqk'HECY#X9Us&RoLlFrC[P,Nn]l0O;l>WW0F_0jtCC0#NXQ,\iM'6c5+??ZRh@,P,Q^O`chmjIMI6;fhNhoF/%pYHCV6SCbP,iRe\gKH,@n#,L=-C$:92RO.)iHhHL]_'RN`QDb/#fH\?X<4@hL$.9r-ZuJlP]!\og(As2i"1gdIKDKo4HIg6m4`??9EA%`[01'H*$7F8H@jgbT^IT\@Fhppu0'L:n-CZkI;hs,0\tAJ!i`-nkg:9N%RqdOF%)dWRUdSco'V7t+n?dA_1c#idU/+'(!)k]cKV^iEkB2Y7mNo'\q\ekg=iL*$'SaA8SD.TEHnK%GkBZ%lrF]$n]DNIChYC"/;\L%7:H4@2'K9U\VI6.q3'Te>)"q#ekB$N6ff*ih[gJDnVeQC9=B'kWl)///SH)bA%!S&J\K[bS%p1otkS`9kNLEl]%X-[dH-s3r0eaUiuY~>endstream +endobj +88 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1187 +>> +stream +Gatn%D3*C1&BE]".7UEhZ):eE4-/lkZX\*eYpBh4p@),:l:tq\?TDlh:j@F2&>-U@[,SN:)chqFq"<@?$niOqn5/csi,6:+S?30R#*lO+Y+8A*PbZJbU=Cs01q+r6LGa?MO+;oqKGSsViaOIRs]BVUsRCQUlJ0=d8*#;[Qj?ujW5DO2jUX2;T#ip8<*/L8pdegg]32SeAd\C>[nQ(ub)#oYaZ\+u&bE]$"1o'c$3\kPeBI,'BnM+/'PD$^N]b6`0g3)Leqkpe.h-@*$uI"NBB8\$oOC4%)%Z"3)b$f.)JJ;11Fe87#;6got\5Y-\mJH>EaHo;6S>h/;qLf`iMgdm(mo:E"ri8;,%r@i!*nq2kZUp]QZZ\6Up(d`W&nIaO_e/.[SMi1nokSgYNkmQgb=k">[j:@C`61gCQ0@X13VXhdYd4pQF@f&Y=8G,qkc%Ppq''Cp;UIH3!;&4HlYrna`[H+L:kV-INUrqj6nh)&3CE#C^^@EQ4),OI]pr2UbRI[/_0j/m]Gb^0PAb`gp1mY$;G?"f3MlOCkj>JOjtCELE[?1-B$4Tq+JV=/DW:np\Z>8\*p((-Za)r^j#"W>E#6;?)UD[sQUAk9!oLL_Z,B-"Y4@;-,M5MuYe^lhXB-c%\`l.fcH:D6Y2+dh*"<@T:LVcj5$>\IEtbb<:^'2=Bq@_:`HKms0*CWF3Ws&jno=!q//NJmhl$M!)F:G0WhafM8$J/UK+:eY05]Ns;_2Pd[##k:0IhqF!JiTD/a>L@m(K?-&-iT[>G%I]Epe1uYI>:(RWiB)@b"56a~>endstream +endobj +89 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1147 +>> +stream +Gatn%>Ap!'&BE],.7UYes%'(8LW5&i1\(VA,o:9X9f6]I<@`q)+Y(UI$_d88)PM=<#5Y-_5ofKRX#OFECFlc@+;%Vno>QLL#DiA7k[XLeroeVpM$L-A>"PSFItg`1km(S#6@'j:@bfWY2V2P\_+Uq-P4>&sf6VPV.[Db:H]aMU'Z!I5iT[tpop4h0!$1GCQ[emb2KKRaEj7995(VZ("it#,Q(K1Hs2cU*L.G>2+a)m]#(W[?:g&]nAr2@.bY6[bI9!$"ZgsA/0p:j!e+dR'rb,%K=S!lPb>hiZ%=6eZbm!1:n(S29:P#eQDUgd!eZiEIg^m_tV9^Z'&lZ+ZefC(5KCB]1qTSQLFLm*G!m(7kM.b2lO3QR.p3dmtfTlnf?#n>-TY+FT)&JCuQ(uDf7%V=[FP>ae+6H#Z08QDMU9Yie3dg8d+\_X]8bZI1nY/-K:F?6?LpuY^e^'BcdZBA+p&DLsf_8L/nY;Gsks'+">Spi@0/1oOq-ndZhHh6f(CA!LUb65JJ:E.i&+"tXkXNaU`0LP$!Y4$66lbg6]TLJr2]R;'endstream +endobj +90 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1249 +>> +stream +GatU2>BAL\&:Mm.fXHob^7g\")"O!>B$0b3-FB>hj@,1IXC=h%Os0JLG@cR41fddB%*[YQma9^oB:$F`I9?:qEe'&S]XhXh,+)&TFk^2h]SAG0PFj4=qd_g>\3"L;#/^JT5r[MV`VjZ_mAP5$f@p0h5DjeqAg\:OmDPXX'K`%fS(KoFX$[cMYJtO!6n02aQ&!n\3mYV$7cip$fcWNKnE/o1eW\+ZOda!f!FV8hNRb!dJ64>$n>?L`-6G-Bd&[]":dc]M[]&?G`#PMV-nG++H1D?&9UlF%CaUsHhD95P\1l6);6uB;Q8fM?KluE+1$ok+siuQH(cXL-QVX%XUl3qPdC!WRoJ^l_JRJB.KnJ[(KCNNeUJ8c>Of*)RqqE>s+.3R>T&mWGG-#t:(g*UFAe`Q`YZ'(,d^KW>'=^rhr,J+V=M4(Ya+LIKV):aYkYmT3tfmlMrVLj+Q\r$-B?C:&?=GB]5;uC#o`4CY,.[R%EM2hT:_@Lc,7F(A1m0)-e&YIDBJls$`[d:g]oaC6N]b&?['b=V&%YnOLNnF9nY.QrBAb:tMbYGI3gRV;1m<[dX=.Z9SZdDAnaS\!^2Gb+NcH0=7BDGFRj`MrKF`8K#8s!A9&O.OmAZ3mBMQ-rX9RUDMc(.5>*Cg4n;E1fK8"#sW(3j-!58+\].crnPc*0WKjjV$'G,J`![]0if!A1"7]s3a)jMSBhJZB%uo]cMPjeF,ec@_Jne.J+MTkb=0\:1YQ_VUg-Ejl8d(b'.Gg6G#_.S]"\Y>UAY_s\3hF@0[#%Z)pY]\iB&jZN"D3,F/h8-^&m\G7?J`P#>6,OkHrT=:^p;#jGJth>hA2%TLoK%2+Z"OqgPcRXI0f_eWTReHUA?ML>'NUQ"hbE[=HRq-A*-RmS/_-@r;oOI7ue+1Ym9X/l)aBP:no:[??QI/,@+jbUV2jTDTPsD,5qY8tqn^Od;;t(b)#508K]_dNF3hHeO9#mf,A[2ts,!EdtSJnsl3BTaKc>2rW6nm/~>endstream +endobj +91 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1229 +>> +stream +Gau0BD/\/e&H88.EPO7Z)%or6ok":FOK9H@TY\91kbeC^,>f4hM:>)MXh-g4UD/'-8aZFa2SGd_&Siibb+tmurGTPlcBQ.1.>+OWcj:KTE+K;tTUlFKh8E[a)'O3;0JZ>g4L1Otl(^S-%%GE4FfSX=7=MY0AR4r"o0VPV4Y,*u:-"rV'hb0Ud#?DO$j61[faWHWB#j43TEC27<%Duja$LL)VEj5gO!&m/#7Mg*'2R*;j))o/L1iQ;?M?#OT+O&Of[^?)RC%.&0lB("&0tPZl7h!)GL'7I8mib7J3085m$Knb!njcI'pR^\.]L-5[!q'T:ce+t?b?*d>QnT`a2'^:TNMX^!%_#oj['=aV^b&m:\P:s3,nc9GEV?Z>:2L"=rjf/K,e@AA8l^qk1#QpSg]J1u^uH4*0=&S9QDbk=AZb*tqjP,4[+]5sVf2h2d1QY)12700*b:%f..ea']YML%:)Jam'MP%5mlIX8_Z4[#)d1;5DA'2rNl(AbY;bi7!cCd8%j[#TfemRHY;nKIJ(tl?_]@'fe4a_)`q4f:8VF8@*#Mf.H6[[cC'p9V(mNYShUl/JHEFF$iY([k8[>t>Tpk(AB`-M2-CUf@T5]POS'j2=p'IJ9=_#'5A21qMs-N,\jS$A]g[/#LVe4TUWRYQf@h2jT;,G)jQW$r!Y8iQ^HT*0jc#>`lmYh_;;4usu0Jg5XOj=D-*\`$3?5O.P.X?]KOL!RJ3YihV&+,))H7BtnA:#E$rX7$^JkE\R?F?B^#lPogAJ:t8D!Dt+;Jc~>endstream +endobj +92 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1105 +>> +stream +Gatm:gMYb*&:O:SbY*LtOXdqFP(gjVWI*=*$0jQdpVedjC8,YlM%2s78W3LnLJmKkC966n$3N%:F#faN1Sb+2rLE#n$\2:b*5?X3E?Vjqa1$FecJVDP\f!MhP$nL0`3]AT&s:2t`D#6elSdLaZkmbAo)![U*:P5]Z+u3:fjT)pe5%iqYm[/9,(##b]-D+B;,r`EM2fu`_+9j1-;&9A#$Jq,2MHGX@LNokH([k9(3OGs,*dAFb+9R3.`QE3>Su'JQXmDi(OM]?>'Eq]8(E(:C'TrqDK=TmY<`>BL.b8@:\.BOS4U^F1QQ>HaHVd1`(99"/l8P1e=&CqTFK(LoNQ8+VK$a6$D=rcs$4`&@arX4q0DR_VGmbUY5'E;,gksY%lToi9<=UsVnQ/GXaJc%4U$Yp_u_0V+q2Rk=GC3t,V_JN+#S1oV=8Pc1^krYU-iNu;K/r%aDNZG_JQ0/ZtI7aoE0@p3L.((A9O26\>QP@rD59PtJ`-#;,VEr5[=lUgiWc]R>Ujb8=He4@ZF<'kSj3$NN'(BioX6L#,teWSm)?BIVg4:Y,[sQ9:?Qe'PI1pe)bgNVEZGCP$H6eSiKZ`Q9q?B<]!CUM@/V'Pupmha=K.!c!/-o]Tqcn(P+dl'R;a?`m&?pNs0bu$b,K:fBH>^r0[V3\rWM*`La9j#JIgdh4e+dbP>j1b`U)#LlbLS&h3(Ued'#/QMGT)$7uu5\U=W*Z+dB$i14>DXCT#9LLiSFN&PY31X&\gH5`15a$(?X7oRt+dIs@.=b$.0PK,BU[Qi#%L`~>endstream +endobj +93 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 255 +>> +stream +Gat=c_$YcZ&;KX;`IA1DaEGhV4@Ve+LPgK\gK*'Be^gS[DmTf8cliVU0`TAk0cZ.0G?l+p+Y[5Bn,OX;&,d&KMc=p[O2!TJ(t"TFEBKendstream +endobj +94 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1262 +>> +stream +Gau1.969)P&BF88'O<^Z>OsV25e&*cWMObNAn0.qX10QuK=/d`=-?-0T71!&9HY"5!]!2E8?bqj]DL3@`:/IeEW)(;!*:Us^5X:R5rs_0o;MiV=1Cgtr]>s<-[U:*Fh44ArJNjfK-#E>+.1^fI5g#m/hQ:e(<$1JWkQ4r8oBCJBRC>]!RS%-+%GKZE1%t18TK#rT:S@F0;O)BH[jL[gLdMbVTlWU8b1oqB$8s,I,!NS4&WJSQcC:@OB4o]-!XW3eeY4>!3Rdp1SJEBR`[Z&$HA?^CG?K_8\!0%MSbIFl^$ECle*Gke\PR@o;p^aC:?#Z9at"00JR)H7"\:@<@$$:LrXi-7PV:',_j#eaE'-WJ#jQ:LaB^WZHT`3H`_^IoE>5h^N([-3Bl32A."i3:6"JTH+YA(ZK46_M3C(f@Y]KSs$^CPN+22GZD?uCuq1_MHb2q?5+.0mD@h`Tq'N[-b'V#@gMC.CTpYoWu%G_Iip@If->X!3me;C"u2ON:f_;s/:]JZ-UHVNaXDh\>3C[NOsE;Yg5S]ERC:X%bXls9HTGDjCH26P]iqQdLjPeGeplYi8Wb2p3<2N$Qe\/^m1S4h,iPj`>R2'&U8*/!5+_Y%1L?`Kr'#b6R5CG\W^'9dLpU_sn.j4q+UMolAS(?CcI(+cZ7DCR^Q;q]6/[+mCTAp(H87DqATFLLC]fC1,]&<[o2nd%.3M9pKiL"O+'.eVW:V4blFEB>R8KcIn(`\X3ZDRIikr0q$o=ZBVgAc#j3/jXJ0a1cIicp,L*3CO^,F8@R4b`];]Wh9^PhK3]s^CU1E$_]4h)O/ilnc(FB;p;+X!c+,0>-SCllF8"r[RX!'K,Ku7lbTRKX0`Z-^/P'@IkOqtKPgdE4KJkE!RhKs8VV5,84ObfmM-jo(^_%MpYgPShh[/\0tS%EH1B41gSO)971.88!kB+bnT+nIuTaX4="tp0W68RNCJ:L)C&]7'0\sRr,!S9j+,d'+Ye>#@&^!\DZJGZf/mmNKTlE:T`l=m~>endstream +endobj +95 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1164 +>> +stream +Gau0B>u0KM'Rf.G>e-lQ,&f[D:8C(X.BHR%,pdI+:fc+Sh]u.eRB1$pR]cGS]llR^JBaCc[M1_CceNTf4`fa@E9LoYDd!.R:S;pSJ@Ub8JGHVaaVgToHXHL!qTC:A-t%3o^-Vo)p+^PQp_!;c=:HH)]q.RO+==:/!#h-i2-(I[.Q$1KB<^0S<>9#ZHsiE[-h865ISWG=Ha_;SCQa$URo+F+qun#qYYU^`F#3hb[qR=adtb`i&saKZ!mr<$e>?=Vo9hbR>thX7V.6>mQjtKsora"GucfW(dp%6>FF0S7XYsiXDY![s5[[3e*o>jVSUB-phmro86o<#aq%e=oc(`BXHSe%L_%4$)72`Eh!k'0!MkE->p=2m[tV>:`g>jXU?>P$W.JRXb[%nFi]!05+Af*oHUTr^nGsq(l)uCFJ/A0-Ie^GiZ0IbWY48mnpIEjmR6I7gS"B4b?NQRC^[.#a[m8^rbJTV5XY5eEm\0Yu^]ApT9\M1E%ldg8it3SC0#*EnVrO05_N3(Ujb*$f4S(56/#uG]>`uF-1#o/i\^a@8WA^nGQC&4k@*up!Abp&gs\1\1sA1GfcR0^$@r@%Tb',BKUpg=ect>*HVCCS;bb]M3q>UVpBg_bLJeKcOYT&6.SORqbLQlX*7q\i2]:Zf9@hNSui+\mdBAr:3sMi38lcJ7meoZ`dO,409L(?CbBL"GP2oWF'n$IAh\>mB'EG*)e%a+f`-Z;m4cnp+`caWI6D!3%r5e$>Ru-D;c[uWAOi.<\<&VFDLF]r3jh4f.^IUS%k!3V#hJ3KS36[#fpu=E3C?PBYaRkge7\eYZCGDfom#;e1q?b]GF#bnrp)*ulaq#FB'h[G"oYpbQEC[>,'?hJ2OV?@VDBiY0&Qi(GIe.QK3!s!CE7Z+"36-9\5To]H+>ZuE;<9:;@5es>LR3T>o#8_[j(3UlCt-a;>Aendstream +endobj +96 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1156 +>> +stream +Gatm;?#SIU'Re<2\3M_j/J:k/[gV7?.p=bp1Lu?/VZI&id5Dq8F@drV2XJ3IP-PeFQ8BkY%2cPKE=VG#pKX/;LMu09=TS$H%"A.KJG:g&!!t"rbeCrlg!Pj*ZC.,VO@3WBVUH5\i.0tpeFEmsP'/qBTW#?jgfD/Fki<[S'"ARN-tj*b>ZV+NBeD/UKIl#sloWGJ>IZoeh1Z97/`c=28`%PN>042%4G+$\7afPQ,[*pd2!4)T/msWb=&omm66hkTQ`;4Ir8XS`Ngd\>[W/QUWYFJm5B!"af3I!r0q5U59MT)u7:mD^V":Mc6R(]^&>%Y@G'nei>C&B2J05d\`Vj'CDnZBCKo[fu%DOIp*4;4SQn`&P'Dq:Rp=dADkB4?8V%_5Ei(Hfn`+Ot1lsbo&iiO'cV(k/`,ILZ"reQCe`m5KcKH$-l:]*dZ)!=9qs"<:?qp@qIbWk_d/KLU7nFiQtDc6sDF;&hDFLtD1O/5qpUf5Pn";A;a(h.C@MO&?eBDP*]#uV)RSh7Oeh^(hPae%`/'Kg02Q$';e4S090As,WGr%=rJ>`[*!t:;E[Cml$.fti<4pS:@J^*Z9WuoW404pc9@]]!&GY0eglRngR^37Qab6E2VP@2YP-dm50"FKk4?S@$MlU:A58a1cT?"734J#m.rucMT%E-LG5\4>$KppQ5Q+Nh&THItcd=lp!k;k*ag`uZkA:Yt`t1]5?QGgPZ.m3Gq'jMp$V:Y:^cCL?l!rJI,UO'aL\'8`EIM7:+'@bj;@IJ@Ok.,,6dr_,Qf1fnN@(V:bpC@)$-h+dB+[7.;d"04)e;ZtM.rLP*>3^R$$o/0a/OlqPSXD'#]Db8U*ee?>P>S#P&OXffNO'>)P9(m^6O5VnQ`)`,DHV\a3`ieqt]Z(2-:Os"L[endstream +endobj +97 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 691 +>> +stream +Gatm9_2b!=&A@6Wk#/1\<,*'T,e#\RC*PV$mKW@6MZXW3ASc*T[/9S<_CF8W'e5IU#mhQsH[>/*Qij+=TAOo'n2Z,^2]R&Jfb&HC7$)hJ9fDl/j3N3%.3sKMBt?k""DtC7L+?nYQ=%d3JU/``cDACld+e"O8RRN*e@#Bj%peLp`O8dt5V7sHYsq)RhFWdidlYX"5k_o>E)4gK,f7M7&fYo!0#o#*jW7H0+84&`X`B-_-nJ#g^j[a?aDK`mXO+?>KV30b.T:pU"k;*OD]O7d;2?%J6K.T<*5>ktE1Y!F!^S4OXT`\_LPbeY/fX#/jS.U/R;Rb)AQ19>q/u2"%.#_;SFTI[[^B(HPbJGe;Ok'b^9q:t'TN59-nC"7^rP,Bd:DRlen'I:\P=.Z6ur[6qWSd1WCh8-]NYYBfZd^8;q!`mgpZuOp9kXk'mDm`ME=Yq5R4a5!\S8ae_jO%=@QjPeL=aWoF6#r0e_Tr*SoH20u5Y27:1m*JZI!kr'AHC-eHT%scOSO.p-^2-h3J6'>NI4<8*K"pD->_sA1`;m`M~>endstream +endobj +98 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1457 +>> +stream +GatU3?$G9d&:Mm.R$XCUHPRBdcf?Z"Cs-OuaTgJ5j@-Dk%X,rpYWoc;/-OJF9V4riK_[WV%tmTb_qkgCjL^I\HF2M]Uc,)B]q0-KP(qCo[;ao(AC;7(Uh1knkWcl%n*a7KSlkkZ5Z4/-qoSa#8?9bg#Pp/?t,7'5/'Hp57C$A0#%]"[t2EWR&@aUMKKs-h.q_YcL>Is#G^kjoEA)X\_kU1cCMO.[-s\hqtUNed.<&>7CRU[]$&SHuAt=p]j1"?AU4^Dg]_,>c)uNt=C+cX1uOY$4eMSCc)pSnM@nTRPh/3e;@;,5d0.27$OZ\5=d+\Z-&BR?$;Ze(;(gpOh[hr[j#YZsXc<#WCiMMOLO&9*>Hr.T*ZL_6ena8Rln.GeT$S'tqCrQq:\N<!$BPjgWIZAT2Q!V^noH?1EG\oOMQ@Ir\S1HS%?A&Uaffhf)1jdOVF='JJm2C[R@V,_KQ"f>8"Fk,V!_DBX-Ln"M:O6]'6bNc!=_NY]`ARs#6sf*Y2OSrjat4tS-8A/#8V1B/fKjME\<\R,MW?A9+IlKG3Okq;_Kb*Egu,rPq%<]!`bD4peUT)nQn#Jc=eT(eJQC-?KRHl"GQ=[[Ts)-51=7Xo-!H!ipIiRP^g;6T=YR0$CN`Bb2e]GASF464(kabL:@$^=9U-Vc$%tll`:_Hp;[jh:oMf(JTHViWFhkeXW%g,@uVdf-T.R:X`nib#b_=-]FAUTuNS:6_I+]O+%0g[998$/uK9:%5nql'o8Z0cPV9/@`^<&KqWTih0&C&qUsd6ET'gOO0"=?f*nH4>Y0HQn+;nVG2\$0k-:ARLdS!DPiAlUjB8p@6Q%DAa\?sB.&?P2:R=u/5pn1%:[)@G#6?N~>endstream +endobj +99 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1295 +>> +stream +GatU2D/UUW&H9tY)!P!?WmK(2nZs6(jn+(=uIG@F;E:'\e>)#NeN\5pVCEP-a/"pllQ[onadjl7[?Utp3\NnW!HY2Whi1U4)Q!Ge@JKZddi=DZj`'Q@&rt75:V&1n%;9M.^/EHusX"6oUM)0ircE!f?DRJ8nAKiBS?PiB'o]ZNK\EE6C;*aTH^P.W<>:5`[KG3_HJa<:?g?4S90J/pMj`KbV7ma$&9;)^lW1]AQAg#b7=J`>2W@oY(-YAaD*(Q\>*tPFF'NA'Tlc8ri`l)VR9UI>D0jsdr%QL]]Z)g70OU??A68Mul`Kt?U$LobX50"IXTf::8kTJck'/R,_/5Lnl8=u#ON;qaiL;eJi`m=E"hjm_FZd"Fi@]sS9"W'M1VSO"s)enD+E9js&qgZ;MNtU]1!/Ppd5RQA:&#:1MJ+.UL-qJnV(#-hgsV]=P,HUKM2M^05hNt[IZs?`$NA<1!Sk)R>TFe$3\82VW7Ztur'MmEQeU3$F8tMd"LXH,P'"pDr:*Q3?5(Ptq,i1^ps[RYaFr>ap#&gEFGR94m>,:R!1^W@Cm\sb9nZ8-c"WVNSSRn](^m:MdQlT?OG&+jK>`1cbke9mK85J6GOUg$0dZj5!T,jt$V^T1rQG85a,4>c$^?D&/fA<=S"HGA5.!4BUF'QB>b1>`HG,`InS$$+\)d:pq9o"[eTM5[2R`=T_Og9Zm<@Qd?+f37mYt`8p1ZL,mPkmOADbs5U:6SXT\fkQ6qM(2Fi[UZF-bI4)_l`BRYW`9UbIMkA%tmUKYe`^0iTl5q,g')jb(YrW.d?]mh-[;fgqf07uF2+efmB#gc,_Q.XbU@URUPPds`[*c.E0"a>+sIb\"3,Aii`H_`'T?rWa\[VM5~>endstream +endobj +100 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 870 +>> +stream +Gatn%:NOu=&B4,6'RKt;PWI+IhF^=ocG5[Ak/KiP2NCsDE[BB^rUd1]P#=j#:<[7gLIh,cf)GBF'0)gqdTh%un1fP+(C;?MaHhq[:\p0$JQ^R7mj*rU7q3AF-7@jDA5%2hY5_Zt8%YVpc=sUmo+!oAfdY=TMlk-9UXBMgDn_oN)I"o?&["_Z$i.TN1`C8EAKGoLmd)N9[ue92L9CPR:'O3JXKgJBL;+GKFHTI1<39Ir^_Y%0YNq4&0#pEoBNRlC["pY4KWo`5(4WOa*J,Dhm5'.@PP>MU$UJ/CqEQ%Vmb\EoQi%Ksc^E#pFUR`)uR)7@HYIrQJUh%41CCF9H>#sS8f_>G;gEAEN=fNV2d+2lEp:4i*D@>Ve;m2iAD+DpKY)-&E2[G7$,eqtf/#*RaZ]ZBSb)B"/c![+pkqmd/,Su_DcTYs8[%o:+7r"3W,IN]4iM6!#k]Kg+,KAF#[:<]]-s2U$NKY'(EtmhKY0UM2V-Pa4\"fWWi8'a8B?FK/c("I>_nG'Vq#s=]bendstream +endobj +101 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1361 +>> +stream +Gatm;9lJcG&A@7.%!nnbW0KpQOIY&`8?p!Kcu!7!=(fq0%a*Pq$l[^4a."VG%,=d:d3mq@#Ce)-\-chRG%15bg2T)"*'*l>#aTF\_kaE]cD3;DIDVj-rIr41Yo`pZO\)^ETfI1a$6KIsU*55eD\RP)aAl#VRUG(U06Fd^\^/'GL;HoPlV5?f%%#)DTHpRs??Yq^?#X@-gfe!fr5o&G!3''/;Tm\8*erh`>f*iPTm>tRK'2H[M;?kQEEK+LkjjS<"Ihs(3$0).%&k\CXT:\aV-GS)=>EpU9^'^5Vu$hQ2;i6V7(H9FXD89*]."oOGWI2Wi%i3ock[r.=(L&Ys9'pYQJ[J:n-'R[?$g@.RK-a?V6JUVORE!LQHrQuJNcJMJr%Cu>\0mQfQ[YcWPNE^LG[HGT1#@]2%!pC3NM?9(qt#'ou]eRnWOBki?*?L$$t~>endstream +endobj +102 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1176 +>> +stream +Gatm:gMYb8&:O:S$ka89Mji3p'M/Vp6Q7P8!BX=k2D8&RCmH;[CN>cpV:5c:s1Pb7W`r.$W?aeh%QSUmS@O_IQO[&EMB(R+"Kl)f?p5hl@"B+ZEX/B#imS(c>T(Kl"TpE<#;P%##*6#;$mP3=I[A?*@3#,@+3aY7S?Y6W8P[!Jnlkp6Sia\(Z&g8hEbQ(+G:O:'GD6NOK8g0_J"Y']Ln0ZDn).NB^7niKRKe9#fCn+$B26P3B(_uO1*EXb)\/:H8agCot$7:^,!OJ+KcX=u?&rdgH@n@e^^:sHb/Z/#TaW3j&W(Nhk<]p>0PC0&GSu\l/YiIPaAG91E.;ULYenmb'O5lE28s_s"CB@t#^tLL7_simUG>b+<4f`=N6N+iS6#3tQ3$m?1SJN/mG)AKkD(&lSorhN-25K]5'(WdEgVf#b*NrVr+D0f:Zc<-$S4N_fMM74fP$=b\c!*gn#TK6U#[*q%kFW-kX2uLjWg9]24]/Ss?I]-H)ga`=8gV[Fe19+PH*Fdj/.7n?]srQp4C5mA9&l?cTO,C@G"J7U#.PI,ZJ,a^RbG5C<*`?FN7=OWIZtZ"'[.hENQLL3KHd"?aj*Rs(][l_m134GbHaujDT,q#1n(!S).X4ue-Uh@8Qu7`;.3R*Cq;8$1f-t7OT>4"pFE$3YL&GNElVI9n*p\;7:>4"SdQ)]U!C%Bg'X@j-cIB-Bf7JlIg:Tj=^Un(9fYkWR',q'Ye,Rd5A;iG9L9!/+@15(c6X$4YGO&XKUfd-"MRdK>54l-X^Q.WCch(J"K1fsG0\Uj8(@Z)mFQ8l2Gj*5=FCm1Tendstream +endobj +103 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1216 +>> +stream +Gau0B?#SIU'Rf_Z\A1qjm934^Ce6(ALTL!R!\KKl7UP.?@&7Y,K0C9tp,c6H_DA'!7]B'Y?\O29'b"pcc@6[OGgU/,3(k.A_W3P`chC>aB+l_`VCc5$u+r03Gs<`'3l33)55p+5"Bri>[e2Kkf<"=2jS_NZfoO`F#fOU^@.h+1(NioDaa!>CfHk,_G,hb(s"2;:$F8E[?SW15n+Bk;&5Kq>("l9"e!.s$]Nr3Pe2k$R$p-J$M13H(LPc13[AsQ`<\U=`loU1P!TC)3kL^q15U>.o100U`&+8j"('kiX>'&k*Pig8ekk@k!Hf).Bl6UYD\l8-eLOR3ZO7L19ABgX"g"G*.]9jgi3ZkE35dLha2:ks^m0h(\Y00T3mQ-&3$0Lh3<9^SS3D,C;Z%s)H)Kp`n`dLT\DSi>JehnlQ%]R.B%]oeb?-?-VMISe4F6RqAKK="#P@de_OdRXO-D#*pc>h:"XC>0c3Nu$bKq2e*3+Gc7jW59e4$r45*8endstream +endobj +104 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 384 +>> +stream +GatUm;,>%_'SYH=/']B.3*-/tpkD6ONA#%A0>>s0_J6N&ZlOgLDqU4#1%ISIdO)Z@kF@!4aC;DlPr3O*EI#U?L>[-W;*"c.`%=cN$/=:[$fkf`(<*Y:'=![B%C\mf/DJ?:J\%2j3iV(=B^j=<*;=SS;,&CqiJh2l#Ytf%%:qZL;?Uj])>GTSq:>)*(<)qVNXhl02TX6O?nXE6o2l?1!f5:p.dWVg,(MWuZHgi>*+uID9>YVA)LCpO1!:F3n=t@2-dCl[H'/%hKlZ!k&QI(]~>endstream +endobj +105 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1372 +>> +stream +Gau1.D/Yn7&BE\k;bSF,lCJpR"`-`dV,;:M[F`%QOK+N8Ada7E"En'oZ/^<0Rp?;FW8Xhf-V';9@-9a1^NhMc.D;,GT2ks\&idU5^jJ[n@%eB'ne'_+#N$"nG@qUW'V9PhHkOmAFMVHiUcYD%2iuV3%72C`JJrQ/hn(Vb7%VlFFBI6&9m3lK8\rYe:$;&r5.mbR?pi@4XD0PY.F`:pCLc^fTN,m\S91P@\Q<9\Ra4b6itVL"]FUh:cP:#u/E(Zf949_t7Hp(q;s8-!JY#/VXs^Ij*OG#'Ro*OF@4#+GIl%)CVCj;#6&o48dkR]*kiARr?F)&30DKjI*GHD=R[87H^(McJ('pX$h:gl:)f;f?"Cn928f)?(i<)?2VOku&<.em.Q\O7f[pa0L>U$AJFqX7rXe?;aiojTJ">3'S0rgs_8?"_k75?d>F;XHHd9cMC!B8N-?)Q+i=Y3:pCE)n*gu80bIAMqh7E[tmBSJ#geZs4DF-DpM7#&;b3GeU#ZR2N3WjnjHfj%Z:0rHc]j*(0Ll(!>TaD^C>jl7BtT%8d.s"HHVXitaq_[mg=o9Zad"GItg*mLRscm5F&\D]""PEZn]iL#]L*ZRG]90q7E.A'IqeF3dljmgPSYEU$g2qSWaWfpU`GK.4khHJPl::RJ^pO3Z;$3@%#FrT!#a_TDo6Lu:h<*:-_\"sbr;D:pq[HNg-5&^DEr^5US129Aj`'KOcTdj;ClWQEG\p2h)YAC]rhNmRr6G)m5dWoo\9H0ZC`:1;fTi7MN/%S>It_(/2Z7;?#?:Z53SBL0*J.;rt1&Cd)rZuUc?D.=gI/^QCN'u(D^;a'iTh\iB5Mc??C2m7u0e#a#fE5IA(DRR_@)YZJ\@V737&q9TrFfq[t+T3q?Jm(oYLP&;un(!iejb@^m6i+aCM<8$<[\\8!#nHqendstream +endobj +106 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1020 +>> +stream +Gatm:mn_[l&H0m]EN1i%-4$lG3,--m?eV<\!KHfP&qo?"H:q^d>bM,qc7*9U/7B_sUfPVapR^j04e%!q2lhCk*8i5s__VLU6Snu/3?CTGi&'/uhdOiL"sF-+^^7nE87,3/@4Vsr!"Sa/HKL#[nA#8lgS&.o/Us!`DGF>9\S^KbO\Y&e?t'liiPu&c#DnLg&H.4EZc0VRHP=$KYH_Gi(gEcsMT(5P!F3R_TIts1@=FQ^#2pQH>c$pq-X>'5)821G"=>[;k\ikRR5Z(0H0W_Qc^Q#]/D]X;07V\q"*>i_5deN"`#o/p)\4k-j8hh?E@MfbSpB04`i&[1bAOM4@UE_l/BtALUi1c4c0ni!IsOlLSji^*VCBT!)bX\QcL_ARQ7G^<[i!*;XcNEN4+u>r00MFo5$h\I3lD&M$TtKU*7IY;diZ6n:KM2O35/X^.SM`@9$85jLnk$+9P=\&<*o%uDbE5+0OLk54l;om1PJe]s_hm$pV!:%sfuc+`I]Y4-l`2,0FmYW7dKO9]DI<8=bV0*42#Zt4VdVY3\W:UO5LZJ-g.fq2HP!e=l8Hiab\`R&HO@OB*ZC*=G'h:%W\gY0m->H-dU]ZVec4QR:A7[@O*0G%":,%QTpoBQ\*57]SaQ""B.2.m+VRi(j^^8ap;)+HMG*[c15g2L:5IbBLWPpnO(.KuD=ta.fAN%*d/T_S?7j-L&-T#M.ZQ9_>gE^LKqQg?paA4,B^b:+IbHBI(Rq0#`^tD`['Pf+JODN\si2[dnG8SN=]\0kk%&`iKe`#\uo[##%*.iendstream +endobj +107 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 792 +>> +stream +Gatm9?'!E!'Rf.Ggda5)lkaO/;+05N26t^j?Y64FAP?a63uq`!f6c6R+_XBPh2jII.64=#Hs@aUqY8m397ZaS]`%&H+$`kW+&+!$mU2GW*[gsWaWuRN2P6l24JdO$<`_ebGC9RT-^UBII1bbO'mE>f)($qrZ)*<-($P6;g9?k;"D^09_?i=d[tbR1I[DB6M@9f[JBus-kA"ikJ7*+>O@,P'lfS3"pWrXcPA4#@$ZHWcFaK'WRPV)AROZ*:MiPS5pAN=$,-'mlUTjOf9o2pC1MA!ndn\n1EL+^(NrHI:#@D-81O!H(mi34%>0lR5L5M+-s%l$X?/#,KPR'f3eK8Kru1+>#'R"raSrZ"nL0\s$(6h='cklI=pZ7>bYl&8!.Tm_G[&WT!h%Sm7C+gF%pVJ&T/Du1u.+H"FhW3uhPhVt\s/uWZ@1rdi2SfX*9N8r$SAs#V1+./8]*d2N*p[!oG(uYbg52=<$4>2C7^;5DbB\D&+""`'e9u1b~>endstream +endobj +108 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1350 +>> +stream +GatU39lo;R&A@7.oH(H,fh+eO0IGR5WMF\ePFYB6eR!8.EZ8eU,tF,,]YAo"Jmh4d4"2Qjb+ItNh+t3r&R<[1$K&K!\=1;1D$3]Edi>)jIgFLVE&0Y&3JfXiVIN/PQi6ILE_LDuNIDS-SOE%iFnflk6)^/BT+S8Ne&Glar[t@cF4f(K8\'Oa*=rZ7)RD;fSA1#eG>_2C?F15V#pK#lq@np^fM)@o6=PqmPN@s*_,>RsodJI1S\PNRC2IM"9@MTfOE*\c'A`4U'\^;]lk+Y282qT*CH3Khq0)ng*.9o4jd*c+k]*^MidaeI0P/DOkqatOiP]4)a$9Y!'[3;cEg:a2us/=@8Q=nP5kMEG8S0uXT3cg,^#6MWW#6#>.6INWTPHH%MATV=Ak9s'`WRTW:n?nttH#hXGgkR3h'9)G!Ot]7<%H^^N%l"_=cij?h=QM*He:K>a6VODk_pgdK6FppK%d:(?TN*A&K3<7q>>bcCiH&-VmQ,&<4H;=\R+:!d_AI,@bSj#*IdN)<*#Y=t`6:PR5t:7.\dB@n)_S`_3'=A2%n83@7X5n8X/uJtEl4t720VGW+;'1;%Yo,#=0Cb?dbaQR_(G]!?k.('+Kq><'$e7j9V.>i9!:;Q5<,hG;joaDDC8$n9ZRZ(Y`+AM.YE"!\F=q#cE*T!Fb2]8/GDJf;Z,S0-LATIfDIJ+BFB1GqG(2pW0Y;6p$P!a*64[eK/At5TI(gn1Pr"T&Q8BKY`3d-!].J#%KiIeTsXE(E%lb_[%e6>&p]XCNeZW%D'&,;L7KTJO.8qU,b%.I[%K,*S"0-<4n?P3Olp_di5B=ef3Zlu-HH3:D\g,-,$d4TY'7!UI&-CN4&c+@<^;nfi8o:!5c(Rt`2$-BmAu-TB$HNL$S8[/a;@+8GD`p#`gq4-dLUG3YhoI3;E))Aa]'!T'-26DJSttI8a&ieilP*Vb4aF:7=X18cPU$5Zo?CCQ`I5KV4C[9/bM5.*R6,M`k4Q`8h*,e_fK3S]@F/5a9Xic[5N[`>;!20cUO[158t%q;#gLUnG[B!4^]d0IY?-K$_Nd?;.[12HR!cGlpY(.C9'C.YX#9r>P[+4"4@pDf.(!e-_ti[6bY'f(B,bc+aBiBr'@aS5N=210]jBpS]aF+~>endstream +endobj +109 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 996 +>> +stream +Gatm:?#Q2d'RfGR\5G15SMlB$L:9'XV.m'[&%&6,'a'59!gD7;JQc_DXosHJ.VfA0_71uXK$Gd2CdB5Al&;r*.H$.4`DSI:!q@q"5:cLj]QYf,0,37OOhMtl['kc0K_V/!K42;;^,*F;Z[>i\';lM?h8u'UB<7tEh;F;?$RupN53j&4^=Q$$FnY*7FY#4'B$8&+=Zd>SLNkgL2(oc1f3^=b>&q,]WMlU:Pu7qC>dY8_CC$!U6mNYn98kE"&P9h
PXc=AMmnBC/8gbnljP53ai1=rtYP4=$=$Fh?:.eVg$4)5Do"dUamfVM3Sa6\^l_Jdjq7h#3EdG_XduF)8Y^GT'<;+'b3imlcD*fid"]5Io$o^*Vtk,o8b"NbYqtcn_?DJU$>ZErubhT^>\J^l]35Uj-csA:UbpN.oEe`2Rfi'"-SU.;_.C.R'lCkY_9HRd-BqF54fro"3oXA0^O.+j]Tt_NUmCO>Xc$jjk%8Su,ME43H8XTPnefZ4FlimJ%lsW.$Y1hT-.a/eiZd4>Fqhpp2Mu05;e*O`2X?m3+PjaM)k3qr7sE;P6?U!q83#(scOi_U1Sendstream +endobj +110 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 917 +>> +stream +GauHJ?#Q2t&:F5U==L87<#+@GE7f+)C)&1sD3`.4ef!'$;E11.M!G!9*aknu!NZL++IO.61n4E'87>Z>`@RWi0S7b`[i`;L'IN_?1IP>Vd57FH].mk[NLKWu':HAdg:8SIgh7#@a"*QtIB5&Yr=6j;H5h0g"nVY7KJaR]L#sV'0toa2-K-kd^8eoP#dpREC1o)2;':=X,TCKV;U)Zb\/JktXi_6-PRFgUbWN/!l6>m*7m=+YI2W![Z$/c3;NRMrt^UR,HH(_Kbk[ehVZn+IaaGZ&iP`l!jZ-`Gl+>[P-;chm[jJL@9qlJtNg]=9;0LqGo0C.[OtfM]'7`[anaOWOjgVA%-2Wo@.45_rLJGL]e5j%/5Trq\>$!XaQWehO%(C/^\PnoYBoqX+-m-IYid2%SeYS5dY'd*/RFO>GL*/P77`Es3Rb#E+BJKBF09MrFX#e!/sQgjdM=j@ho3g/d:8_a"K4m.RT((XIh%=XlV/c[gs-M`Or5ZU]Co::ARU$Cr+.hg5ALYA"E7[_t>ZWu0\q?K2BHK=lM5WgBq*G7J02OR*eqctGKgendstream +endobj +111 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 922 +>> +stream +Gatm:>u03/'RfGR\C*2?-5L&L?gSf55XL&',EGC:F557rZ%MFcA&!IOBds"RWC5<.ICZf4\`[:kCB?\KV#BI&^pb"Q0*i*rknH\H"d!',Grg0kOQFVEJN+o]P)L%/JHscan@V_KH(99JU9tHfKubg0=;T$f;g#nb==fY8q9J74'A*k:;_(3.b.-"d>G:Q(Q%!SMF#@HbMc(r.pWfFL\J05BmRBfk/,i[._Q3]H9&lSP@gl]G&(Ks**&?6[%7!hlQ4!d)E!S;3!s-a-lPqa?Bdi7%X5GZ$a(1LGN$4ef:0<)X=3"1B8nb"_AUHWGm+f9b:E)Z8="#2$Fl0$Fs$pY*(\L"kR+H.Jt?FEAn\:,,bT,nk\q7/!F71fA*<@_LE4B#s'.35lpNk!,(iO(lUG8K4&JB!+8(EI]#6\7\E8Q$f@jk!7*,DnpbTg#dL#>VZV.Q]*Z]t3kg?o,-bak[d6DF45T$7*TLi\5!LKPZSkq^O:8X0\,pat;1GTh$Z1c033_4on:endstream +endobj +112 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1266 +>> +stream +GatU3hf%7-&:Vr4ETg'O@8eE(ZL#4]fnoo']/J+l%*/f$SSc@\WU`PWs7\TQ[c_rpP?BUO,UUM-GkZXDSPhGF:E"[`%DZN]mm!\2Al-bq*@2pYDrJ1rm+Y[ShHBaY1fh62E[AP@Mt"AENF41m1Z`/l_?r6kk_u>C>%Xf=W#9!lg(hi>hipqeR=/j/E!OO"W1;F&gMd(Tq)@f$5=r'd(J<2UiL/F([*$f+nZb3>iQ%X-;WmX5"YY,k71YNZ25'TX>BnJ6DRH(OK0Cr`fct/7MQN>gaqD"T26M]GmrkK`7c`O_4!.mdK^`TquY[6K[!:>lsD#-;C%+m'/F8ma;Y%Y/kXj1+)+*LV@s=8-2>p#Mah3Edu9M5'[C+.aZ`O="Ko#h/XdN\;*Xc&hR^ePahXL@.M%\$XU)@gFVI)*7U>W]!jnj?.C.Lh&]rsH'PnN!p@-"\\.ROUa!pt/aUK>-cYD!nQDhc"IsIHPU[2Zt)kBY=14eQ,p.M-7&Y[03aZ1?5h!0]_18NcQL,)-?>B)[tXcIUQa."B=JC:pF"E#arV3YdO18kAmY]-r>r9di:%27.h$.EIV;N^Xf-\u7&jdFeCKkt7_Kr<"jg!e](3<1qr@4>T/K;gOE:sf]bh+o~>endstream +endobj +113 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1137 +>> +stream +GatU3?#Sa]&:Dg-=JRA''WEYZ90eB:X$h-3NHA[7X28O4JPjlJbZ=o7[o\<3_IZ=A,ONLr"7aCXQGb]O+]MKM]gRZn!(%j1'8VpPW["*-DBM1-OTul!DIE7Na3,cq'1NJnHcD,.@]4EkafB_.=DC$t&@"$X>-JmBpCUbHf1m(\4bSZ:>9X-P87kRI^:9^WMJ\X#Hlu/V;H$T2o2L&g4-etn#ei?`"4Am(pW8%Gh\"HuNYmSR;jCk+>BqX1gAlm#AjnHX7^8\tBHAc?7P/Dm]DGuXld!.`9)=f.Lk_u[XA"!OA=7=C3(26c8FoU0[d7Kcf5"MJD@LUfi#7q#7c6r_ao))'5i8YNLjU&W"?`:[.@VRF%r$j>-E)E-\VrHVB+L!@gO57#4]LkMHmr2/auuaC<-\J%7+V/QEjIcIU5lk&bQO`_3g[qU;Fk]fu?sc`;*h/endstream +endobj +114 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1094 +>> +stream +GatU3mr-Z/&H0>XiWMU5cmVV4V)l2p@?N`"aE7af:P1pqCmiD)g6ujS&35!hN'h'I+"X;5OuCLadWU=TKk.@k[]b*HrK>@"&YS$_PrdcbpN7oD-9#a!81p^FY8=BhNq0AJi1kHo5M!n,Y2fa8L'o?=8K/$YNeu^2FC8@_7?j.IEoc7$AAOn8:T*5)J))Lot()7pVj\_=R"=8&tLi'[drY2#PE%@O;YpRELCnr3LO!M['VXI\T,KQV.7E]!IWh!4b+c&^EV/V+5*lr4QLrY%<#=aDM!&#s#0nnt?9f7qaZ**)eTTN?e$"9FkEddiDa&Kd5VkHJEAjbR4\X)kT(^(H2X-#pY+:ZYn86+9rl:R,9X(;KlcIl&imZXltSG2Cn#TH[Kfb,sG'rZ"\3%[oN[Kr^bPQ5K1[LNPO0O4G*6m;a)FH0p*Y,V47SK')`TSWNH%[0gjt.O7(h<:54`<)#ZDMqJ;S-F#c-B0Upa\Vr7GoiIu4iadR2_2:(nnIBJ#caT#?4KP2&!Z(lm=3R*o+IVhCO;mgY#>G"4`aMkFeFqM-IdYR8Ga.7,(nS"$]f*rRJ0is^E@3"#8SS6/ISYEW?T=WAr9(tb*,5bu8u3Z8oVgZ`-h3q'Qg2BoUD_>5?tbmYKJL(%k+Z5()endstream +endobj +115 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 246 +>> +stream +GatUlYmS?%&;KpC`BUATp55$E$9E<+aOhf7D*53tX\n<:RoU-?_*JQk*1-Qjis9W1)R?MsQ,IK+TEMP%c)5X($u6`O,C9>'.E$h8Bt@!U6tjM+UGYo9V&(`al3Ad[:e;09jWju;?(n?4NI4^9(>@H!c*^QCS]+endstream +endobj +116 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1315 +>> +stream +Gau1.>BALh&;B$?/%TuHP-*H>`Pl6"fdtfQnKg)(TDUGc[:%3Lj..&RH%5daOWXiUa=^nj.ZbsmSF6C=F=X(Q]V=CkVF5&1qX\br;2Zb-7@d3pR[hiGEq&N`[(LYY=(U(-0ir)/`+hD6`o>r.BX_TSPjMk*)&h'QZR=9K:C8!1cRXrdH,e.7X4#VU+JX\*a\68e1(!cEEhq6U8W[NH"Z2m<9*K42QA*@^W?0^>?1(9qdUZ+()r*5N?HtMT<*d5>[ib";H?TIIpSn"C\2P2X09^4YlFM3*,$t&*dH$_(>'rVKK%!I%3Pp:Vq<,eH)?RkF'L#tZN]W`b5>iQLC/ATcU@26`iB%u-6WJOFN8jP^b]"__DVDK=;-U#_N&_t9&[C=mF3G*V42=2Casr[SJglR4@7>o(obDCVnK_(m[2*m>+B18#Tp)]'"%eq2s#cKKe$J4H(XJt#f@Cisq7E`fZ;^UaT?j\CN/,"u\f6RmFjCN!N63&otJjooPl&10a7.cnfAp[clfC'p-F.T_q:gpr8<3k*Z/[.`h\)fDtLrHC0hr5,K2c'L@/kOL(>L`B>1:3j6tK$&dkqc$P(m]WMWL8@Im6O`4WZKT,i,Y%Ys!S2D`49EiCbZMf;a#%1m&goDMQh(8JLJUq905hp&f%DR^M"&=GdgaCD]Z?Z3SPakgY2524;<[b_peiZ*/@N+)lCJ\9o2J<_XE_<;E2D%dhsKiRfcI%.crW,&sfc@``npmJ;>CrWc)k2B`~>endstream +endobj +117 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1165 +>> +stream +Gatn%gMY_1&;KZF'I-VWBTs(3&(o!08l(Q^M:SV,Q(lNLJ8OX1h-JLXVsg]IRKc]E@L7V[0VA+>B>t?B\len_$@oTQ\GX-mq(Nch$bIPjn)406jMqR2cAWEm,M#SPX+XO#J6*9FnHl=/lg5MtMoA_uhTC$]?%*%ts#]KT%);g"XTO4(U9]sFORef=@rkpO*1f0VcZD$ir2Z1SFM>">L;:D,]i1$3<0oBl-nn0q)9sohVtFC:NMMZ7`4Qi&MiY\Fj]G*3_^V7uX:eR"b)oBLLLF"Y>1tuMBqA!;9CF?6Zg81@_$`!4:fOrGkAh%+AVQXX"C&_j3M*Up][SU*0.=tp,fhZl>;\cXSK?C%9/eZk_K.:Ud\7+ikh"M/COse-i6XdMZ#-X-=YsOh7%I2?iM%\l>`KG-YZ,c*,^b8d(jF!j)_-[`l+Es8S5jH.O/R36PL26YIm^pT,ZH5D%uemiPYW?`O9i`b?3O-A*EcO8,'U.V!#eG?_VcQ;!>uQ%!4.V/e.*6'\I`K@*hP8q$NL*4jE9o<%i)W$kCmXG!q5[bE\5iPStPaD0X18etE$!U(U`(rg1=p,a,_%=4U7b_bO+;i<>(I(GG4D*=Cj,7F&K+Xt'(q.T]Rl+(\4F#IoE"=V7m5Z6H,Km:`$&LPiI2*+etgMSaKhZBc-aAOA1V_tB$Fn?!=&:arq\HfgTa\.6'n\:VjP=4.ce\BB+!s-!#'(``^cH"3hN9VsD.eYFkDr>ugM)NMI)8"mZa9@ANYRoU$ka`%A=RR<-h%nj/LD#&bL>K:,83eukB2pLIPo4h5%=0g@t=8QkQB"_eKI&i_G(0#BL'Q##k_c$88(<$YDVcT`;Q)+0!LFMLT)WNZ"*q5reWlhl,btJZ^'MhqO>L0V*\i*M609N2Qmhq`DdIn-AquPh52OF~>endstream +endobj +118 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1146 +>> +stream +Gatm:D/\/e&H88.EC_h"#m/@tYp9hcYU+GD!a4itl8JeH9:]nEP3A$MRuu;gqX4=H%K1>p",J`Y)HhGJuKF(e!PAbYdJ"Q)rsN&(6gH/*(Jo"3I0sgfj3.F<;1c-[Po#PNs<0)qTfs'sF;t`uN]1]UI![N?\.Vc_uNMh;8,4IpaV3AKQtPl4*mM+;_THn0mPjWmZRS$PuQn^qCN;..MFq0E\(PV&;3Y<_tK&(#S[.#LO+DG897^:4\Z2$8=WRQT^5Q.Z'_0!F21.)$E#*Ybn;.X5$4ME3jiR2POHBhc"/Sd!/X:V12aG6E#!Qp[="8shG@N&2=G*TTm.>D9%Z.F]kNAG;9LTW`$,,>+2)F!$DY2ogsFA[abgD@Q=o5/0Dt=XUed036+DI7mMb"@6T$fICdd3M@#:"6hR9bcbuH(/T@SOCo3?9!@\2];bl?31iQ2*."ZIqSml*W8;WQPB?]>]2Y@JnrhM],HrP")2==6)J'U?'nVYQbBs',(Bmr'B4<0L8nKH1T@B09dtl`oHImrs27q-4H\R8YQp*G9BO]D/<33S\iVhI>0g5C)"[n^L%]j?t1i-*E)l]cq2nqb(T%`Z#VQSKReQWj$QfVqNG!1&AkH9Zq_$(JuROh>25IB\4'OM0!SE:NBP[7-"eSb?bp.;QkA=JTOiTadG:-\R~>endstream +endobj +119 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 966 +>> +stream +Gatn%?#SFN'Sc)P($B1,>7DL+I'K0?AaNNtKNA9Bd%75pp-dhhLo:FQG&C?3?=&B!gk&>^nJ;'(\JOP1snk1scHd=kKqIH$\)TRI])eJY2mrl=]G*^#7=6[7S?n>$H'F%tcd(M`.-".^ET(NA?D,B=/&V\0/-?;3R(KH0Cj;-]B=K-$;%gPCM#aMFWgcQPt6N.Gb,6r\p(IjMR+s_mP_OrJs",1M5>,%Yn$,SQj/N3Xb7'lRWTQPkg^Vq1!,I2XGdM1,+H9O6Qg.5iX6"]^cY`7K4%2AO@iC5>\T4;A7!4+Er/%='P[F`+_]+)[7Cm_E+.197W#"n]E:a&pE=RnuDc:C3DiUmQ%!X0U@"E=+[\u__m6)F5qo5P-WH9VFW<8S#~>endstream +endobj +120 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 286 +>> +stream +Gar'#9i&Y\%#46L(%2G<%UBa*)E=lmV+IWI,N9bBG7$qU=N[HA*qhf`5Wee2&/HKi$')X7FiAgT\h];5#P1[endstream +endobj +121 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1466 +>> +stream +Gau1.?W5]K'ZJu*'XnUD0c_CX2V@L'"B*Q*$,pdDS`+.n>"]f-]?^D\6U#qEQ#-R:M_lX<)$Wa"mb)r:p[$^h%e3,j^To+G+4H^[KT\6QVj)96\hkJVj`ot[:9sk1Y>;k"d6YNsd`3$2EEM%9faaTJ*kZAqi`0hfqUMYCr1_d+nmX`Vpjmah2#:iA^3m[[]_q)98:qGJ_7SUFUKndfU@E.AnIL!g7r]h*9D])KmH2>,gNUD0jjo/(.<'^WHFd<8^9'9%fWAYV#]!7I_"-)Y(/;7#ETu10/VBtUPe;%j7ShE+KV:RqkUJZ!QA0L]F[6!'aO,7T,-G:I^R3UfZ9+d75D5L&N]hsN?g+@HgWJj9m-l_umP$/Rr.t2.GC,W=L[l3o%nr?I?R`IQ`(W&]G"T'NB.?)>IaoANS\nr1.L4YVMF63,GFK22"q\;L!Y)J)OVlbO6YdA:k]3?f\HhaedB5eWJIZ?ru-,AG*6)BaT0\Vje/Kcu.Z;Yd)Z];SdCg's\c8)Hn0?Dq/kL&VS\W+p"X$1jQL1KII3A,>L'Ym`!dsBN*<[FO$]Y5&flkT8NkcY3PmS=K_.!.PXR%L?cPsN/&7Qhi5cf;7-^@^=C]3EdW.DUe!5`hHY0l>aebVX3]aJ6G#J>5Z)C*I=])Ncfg"-]g)qljD7Mh7dBoNVD#_-.'ObiI6-u>R-_&:3bEjim:q<>,CJs`Om(9)=2.-EpO>1PaZ=M'C74kK`h0D,j*-+B'V(fU\qEGCB1N([?PWnl%mmKpZ&g-mQOEakcJ'5E6+cGh')6A^p.jtrW[3?W$<[>.(jJj[#u2F>"nFq9QMTKBcao!6XY9k!.BVAOk\8o9MmX?&i-8W9(F_R9R!r_WZ"2i=_*R<5:b)FH&g[[+(>6b6:k"+'uaG^Z,fDFZ>DHj9f#+6(R)]f:2PpshoS8q3nl&0S2[/^Ztk+:NUEfXj@YXc@1M78*"cQk2tLe(FOBgk9smZ,4'rh3Wibrd[HaI"(O8?"pX\kd,"sS-_dXM9To"s%aDil$\5W5aud!FhNkVp)dh$G5_Z)peb)~>endstream +endobj +122 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1166 +>> +stream +GauI5D3*C?&BE\cV\lD5J>s/sr5;0PEQ:rL,grFg)JV@)FNLSu\2D.FUOD1;D6Y%++>Q%3bca74$f=QYc\5/]*9Sb)EB_(Ko_'P[#Ckn0_521kII/-^@pkrJEL#>#kpX85\A>$N/9PZ93KI0#1cnDCNG>ML/Y3jgM3Q'd%Z-P,J,hG-Q.uOR'p4:6u3!iM@.]1CcPJn3C`-+P;ipKTj>@l\+?T=Wf>3]]IO5o]B+cTcHQBrXbcSAgQrQK_*nq'T&b$`W\3CoQ\ug!"%3YJiD.i?P-K#ZF#u1Rqha%;uih"N(Z^>U>Ac"5bEhlk+`=8mJ2J0kDD#R:Kr/BbS/:/(3ZI^>mRQk33#!TAVkePX6&r`Tj>CoSuF=N;p1nZSYT)>a=8f=gZdl=I-gu/b*>*8hW6Wjk8eMZC"-oVG86'B\D,_MbpG1)YKi\qYQqjmbF2;(7dmlG,59C-%4$29,@OhQ(V/F9%Djj09h#,e.)r#kFlgmU!Edg0:mk0^/3D`3dGtR,LYJ3$9MOg^beu^l]YCn&K3S)We)LUZFH=T*nmed>gi$tN!N1A*^6,%\I_X`A^IX([I%.o&%JR%:Jp7#9=k_iVHV"Sm'+$1g&o)*b]5?K@JQEMQV8eCk0Y)a=A3,gk-S=0_/#n?fZ@W88n&$15d%S6;F;VY$u4+?gW^).m-kX[Ml=ngi!jZqWWud.b8SUXM``G4[J#<0r0>4aeCY]4NtfhX`K6[f^M*dq.Mm`4`<0QcPe.EWd#eU&(C%jXg>IuP)LoEC7$'6:lM5Eje@17j-WK_h/t$AE:lG*[?a&~>endstream +endobj +123 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1060 +>> +stream +Gatm:gMYb*&:O:Sbfamp,#q(s69.:QdMk)0"/K/iRfPmA`XWqZF@ds]>P-f7PT7`18DRRj#36/I4@a&0b_UgGDq81"'&`Es?G:'f3(t.(68+#V#C&0JI3#ReUe!bTM3fKOKQ`=R\C.l`\kK6Fr$p7]aDV.QE^XQrDrNq`0hb5$4G:YDSTouQH49[(NM\;d\<+gB.#&\r;tI27iMjP$]fJ)m>KD+CL(dPIk1l?A[o&8Y5+0ZK%'dlr<>)W3hJ''=k"@6d7LO&#E&?Ii=2%i'*9f/DQnHW3Am1rK!?RnJ]8[Kl/`J2:EtQoT5.q@6?#Sd[*F79Zg4u]VX3tX'QfX+jR8DMZ!COs4IE"pC,"ckHr/"_XHr?'>cLD"V;74,3\Bksl'jko@5@)A(rg$@Li7HA<,D?OC8"R9Fm\J%f0s*b%0BU7A'sH(i[k_(m"Pf<,4]+Y@_'oXr\X0]!OYssg@@jMq]"<5$9eAqLCSI*a2^r8$`E^RXGt2>Njk7,fspjFg[tnL+a];_/ucR*[++?blstSqCmr/pV%0n'R8^H^O/'P]m,@/@B<[#0*4ZSRd0E@*G?@ulQ%-?=?n+9\^V*7q\XE(\puY=VE;OK*jCd?i$UV9r_VnB83g]bT]/4IF",*AP52eM\/59/&\Ne;(n.U%>"@K0!AX;hqo2(lpFGJ#:SU34$9llO#7)G(fNLM`XgF(oF$[p,5D~>endstream +endobj +124 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 501 +>> +stream +GatUo:N+`:&B4,8.HX/O&`RI;]A0?r'hBI`Os:e'SKn=:ZT(\kI!R-mD8d8J#9lg'm'5aPr;7`]k98ukE<*I*]Xen%$E+mTgM@8jHe?l<*uf4V.2Bp%A4kZ+5/Dr#0#VqNB>n@BDW"9lo!qaT21"d]8-p@m2Bb-K%Dn_5n#t[P*b]Lo:k+,oHBS0Dj&31m=X3_[bn)?&3pXJU[Tc]EnkndGWD`"7OA#p_D'`^]RIaT_WN)'A5@jSHXC+Oo^TI@-9-e[TVlM="Brq,=jSp2j4GClD,Q1_LX`"nA7ca!BKGO[GKX6F=sJKO,MI)>_ntaLrPer,i#?FaWY:-\\j\M+/Rmek)N8n.BP.biGK5O+6VeqY$_Guho'MTbWO?T\GJ9RH&Fp.fb9#\74I_KgpkQoRc`pfpGWm>5In/Q0P:"JV_X["?*"29l[@g%HTtf=G#1*e0K"fendstream +endobj +125 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1100 +>> +stream +Gatn%9lm'h&;KZQ'n+i7p(BaZ,ct1QIYtW\THnZ!re7+e6^SqJF%DRYaI5QuWoW#h4pHhek<&!WGWhld<#PT#8BUI(YijV@Xcbg!t1"S&75"NmD>SnJl/iDmQ57GVVG\!fMD-qum>'=-IY%I"rP'nFmD=LdnKpK+CQ!8h$!Z1#MOqD7G=g]MhcW6De^*MM,3Y#-VWgRi:^cnq&g)%O#9=LZ#\[!BtRV-ud(tL#-`W;UZ9a0!r4QbeU(&,r;C[0\^W](&ZHOmLJsL%ATRZXF>eS&fK1YDN-G&2/IFu99j)RYlG-A?s49['B=>@fh*+)?[]1q(\SL]12_KV-Frm!!]5k.ejSjT5SR<*)@&P?5@I('d6Q=_>77d0t7-?<$'fKDR=5\1!VU`cJlW>=V&fS["X@7>JUs3Dgict^R9TK.KMH"!_g@sj1aE@N698qUUmKKk>T5qo,q!Z^5Bgm)OjbsS@+_5p\TG(&,HC;*)/&[DXl?GLWJuo&ZV@B>"Lj)M58M"?Y*A)Va;R5@`6^Y-eKiK2RR1tBe2Ca-S.jVK(fK:-ZpBUCH_%Ad=U8igc'o0\%CCLkrU[%Z!:3o)=erheg[2B1@>lFNi)#XZkE_ioFaYIi?f\7M$/2e)V/H4j!msbXE+$K~>endstream +endobj +126 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1170 +>> +stream +GauHJ@;jmY&H/2-EBp`O.o9B3AP!)\!F;-&!E(bD?0oabJu-aN$]7nSb,tF(GGH6LFtKnWdn5-!o8\3Z]Q_Ch+Jq((?Xi-)FD-qlP-1)3g^g)3od"3F#1Y&.CJN$E)/6J_.kG38hXJX1,f]NVmX/j:sL@Imi_]=2N<[B4c*BLDseFP7f?]:fXI(_!S!P<)\EO47#\:,6BnN<(gZ,W_kcY1*bEVfpU,)Lr^gXc`D"0L-Jo&3F#S&h2fB"i>/ZGXT>C]PJFSpM0Wm('3a(;BZH(d3**OWWLJEVd4E]3E&Xoi/iuASNDhsp`bEaU)NT5>7D(M6*tZq*7ZXk?'dHso&\KlH+WLZ@p+,+IbJYe:5Jsa>mr@IVS%@kt0BJPSLfiDIZI3a-qhRXTYD(2eDah?_nOn3iF"r3Lu,=h@8T4T7PAm1eGO)\C7+ame1aNk(%lp_9B:/Z&FLPIJ"[YNZZ2'Sf.!O$l#]T1OaJD@A-o=g:@[A$gdj;jF-i*$nGBV\&XX:`6*F5gE\:R^\lGO#bhBdec0^Y*D,XfP2UIn^aOeM5umH@0$_8uO&d5bQt5!ARE;u5hFur(gKU:*51oT<2VlOYbJEjjf0QBY?2\4Z%'8Fdds<>AC^aY]_mMdTI&L!8"!:\s'O".Hb$=iU*[/;FfI`0!n@h?Wi5+n-81^/C'r*D1DFiQ]VY-Kg)"(X\hie17Nm9g&U,@d/eWMAATbjR9`HJ9.pZ0^_+`19uP6Eb\_N,)V\J;kRDS@o]mc,VFBpW6TjO;\',Spi8CVNftUm$JKm69Dm"OEu2:gMpF+GeX=@a2W#PBI:+)6-Pr+/h3]Y\Sisrr@oVK=(~>endstream +endobj +127 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1045 +>> +stream +Gatm:9lHLd&A@7.bb_=<22&pcE2t&Nd56uc`_C,nnR[?nGS=KLZ8diDr:eKgm22`JD29`OBaApJsjp]F@_%OGV#q88G;4-[.uD)1iJ$VlfL;Bbtrac2l@=iRdln?"qZ(Dr&s/$=TMFs4MQn;ZDtQ$uFkrVh]#B#kj-%GCGJmG_#R^3=_/BDu>8A;!D!7[T?WA*&"()CrE,Cm&aP&l^3i,2lKH`m"294&5V[Oh(oaEW.U<3Y4jUJYE@5-o@T1dBTE/R:5;Zoe)86Wj\e?Ts$rKhQVOdmPAr5I,I_)=4>E3%UfJShrUd&W;GWDk=u)CHc4pg6h=Tni$'rb!(Y&/[a0To'F8m]m+qAJS97;,s5TAX_2[B@ME)6E-I,EHj\(L#5P[4b3KZ33196Yi],1UM(pZ@Qega7@@iaY[#$MJFW()[1A`4LLhWfpE5U'L`YZMO`/J+CZA67I`k0n]Nfj"Z'GT0!nNbhY:mYO.[r8UiU$hO)s`dS)_tc/]R8U5YPGB?#E%mWQ[Jf`#Ya$2L[tU?GZc;l,R`GD!^Y)C'FCh1Qd/85sD&j,u"FloOc?Vk7<:K,8L~>endstream +endobj +128 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 922 +>> +stream +Gatm:bAQ&g&A7ljp/F!TWedremOcZc8ZCbU/EM0[7!%imF6XCV4<;S6o#;X#&F=I\21`Q]?A!f&*prT"7Rmp__VKJJ?/khH6Hf8E12dS7dT*+8IA&-]JQ1VoEH?2h1;^uoIbL%N\5"R=8CZPJ\>`E_ib/J<*cPOBT]JT!`t);'eFLQNSrG15kZ#"cSju_qRd4V9O.`"C(X:FKiYKFBtgrIjJ!?Y0Z[-k/"IWQj=gY$4<\rl'W:1*\/1N[MhuSe4?O"_V\2,3OuQ.N$R25"o&;J9P)/nK3;"E3f%@W_JeS'gW[dn9M<,ki3!joI`G+egA2gL$W8:qSb[nt7r!7U\.$[nkiG-@ue;MG@3#,U(dq,c^gAQP`Kij7BQ.Jj8,i;hg.!8G_\KplQ28+OfU0a&'$')hsn,$K\:3ur6p]Bq,`KEX%^a7,)-/VXeBTflom(4QJ.LkN+&>dYk]N9D6(qO-DqYUsOn0?*n_AhUS7-OEO2C.u1(eeM9=Wg2GI(:*qQLIFs4g(*[kG[?;^(k`G[2<#k*kJA?#FsB2e<`IKSEj\tmb]i?lf8,?26d#e.6^H>^_,tF'F-HC4QHk2=9endstream +endobj +129 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 412 +>> +stream +GatUn5u3+e(l%MWMV!)M'3X'!aujF/*qZ+gGk$CP'A9%_[`n70/^0TmAK4sDo[((lo=Sp#QlE]1dh90N0q**$JbT=pi_L+Mi&::0*rjnB,KO9X8's/6-bQ@JBZtq#Q033@_hj%"!p8)30FELBTJOo6p_GnC*EG/X#2;Gi3:h,[f&eI"4VdWMiW\>"cK8gOlVnB3Ki>6f`;3kC5\pR.Dq5;P?Jpf9M:4Je025BsQ*todP&uQQ1Js$e+eMT:A"^"nbG)tM;0f"s%&g+>dP.dq#I0KV)!iIrlZfh?17aY^+;#Xs\uFfp:$\=`3a,.q%+5>dQa9%V&H22U7^MVlB@Y2m)p7]%W>A1S@'aI8<`ipaY_K.Y\>~>endstream +endobj +130 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1354 +>> +stream +GauHK9lo;R&A@7.oP'ZFWgShA#`r9M+dLPB?jG$E*k3VAK,%pRO^naplaLFmJ4sd:O56N;&kf[il[H%LmD3H3GhI;.!8IctH09`[KjY>GP"%]_i>g$oH0cT%=\?)msZ@is*fU@cJJX2K?dm"!J4qirbuNL8Y=g[X0n[C^$ONhIP0URG0[e6X0"#9>:NN_kA7T0Y(Nog-rRsJFK"]J_U.;d>f1F$7E"9jqPf3&I)7HKcj>t?)N.09C,ta0)NB;ZJZGf*g96AO;;u;Unh7'6qHRF-`(=L,-qMnX]DBX/\rFHjXVr(<%cQ8_Ia(52oSZnN%.!&]LRl_4[;Q+P,HB"0;#+j0o&1'_l4N8pJ>O2f1MbI6EUE.1BQ,isu=QtL\&:kda@a!7R>];U#':%Sk!Y+,0rh#9VuKe=h"!'S!8\:fmLg?#]S7b7T,KB-qE2o\k'\4IG)mAYnfS]*_=UWPi)(I\\Hc?%s1,VY5-6LO(Ep&E!$#LhK%i2s;NMDi%VN[dEZ]qHB.B^#n=L9`s]ftOAA\qb+(fR1*%bt$7&'OrF;0CV4YI?fAipoi7.BuJY[A(.-8)%5hSBpdh=R%0'VlkKF^&#qCIaF1Dg9#(Kc?T.p3)_dBetM`i/"$V2Zt6:YHLV[e`pkDYHrk>.8TH.O,K.[bFq;O?jQ-^e->l$5.ES^7b&-"V*&a8[D>)tsNhju6Er8*pVnG(_aiB6rfliPrd8](,j8E%J*^L$b5'`~>endstream +endobj +131 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1384 +>> +stream +GatU3mr-r=&H/38ifIW=1F0-ah_>[MNT4I#P1iV5oSWa&Th[LIM,['h:&&L4W,h!<^Ks3*4+X>6jeJYia7Z`G"!GXEK$c,,=,U=]bD%f+nD_=@Z)?a:Q,KqT!A/8(F(q3t.-P61`_=,_^$,'R)is^;KED/*sU)$."Sqj?lN'RV0t0H@s3$6;U;_p[lJDbN->9T`NfiGN[!KYPH7=du=1rl.'O8<@ADi"4UT4^+)W92h=[eN6=*f@`JfQD7+PS2,lX3=%8tTeTH.a-iY"L0oeq7go&,;_s(;RP0@`Vbg:X*Q]cF\Lo9Q([=p>f'[hL&CtaVHbG0bN-SWdNC^f&QREfCZM71j8:<4=.%B6s>24gh6Bh4NI:Xhp#P?4!V`5o0V9=.gG/RXeONP+iB3O_4:87bO%t%P+IUF39oFc94l7bctEamsGe,JeqO9lM97Ze,-oRkdOdA&M"lB=$(1tPtSA9`TQ/'p2r0MR#(",27H5endstream +endobj +132 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1040 +>> +stream +Gau1.>uTHB'Sc)T($DG7Ce#X6!4fF.r>6Oe1Xd:\f&RCNAiF_961NS$lpa0Cf9Xop#)6Ehmus:9I,I;C?ClQU"i.SIlJFboRLi0.CEKu.\:R#NpUKD"rj(:2Y4!D)H:L-0Ji4Nj6cFCoXg^EQ%M7`!%=;('*)ml(h(Drg/4"hb_au=5Ki=3rT-50b0Aqe'Ht)\q#_#,0`@0WlCK%ol<(6a"B:,J4ghA)[VBZ"k&Iamr'qE`F_IX/-(f$L[\SuS6h'L+m-/e:kb'N%c_J>1KUG9@=[dd1&,O=gtpc<5GtFt:FqmKLmQs81-Kb!f!"4pj$H\\cjML5VbrR!N,/)kKlokSFgiUn%7g?!>&(1H(P#HW+J01gjWII+(ie:kAPV-5=9#h,8X:API&>&@SuK4GiF#V+]0_TfXgiMmm:mcr/X0M2%#W*(V-EMIm==)Ke0[u:9HH]Df8Wti2TR)Hl&,Vhm$Mbj/b2$;d5_*JO%'U2kbB"I%8)T@1,$j]>DSi.>fo:=(JYQ"k`Z2?QDYa()h=Yf]3)C!$G1]b:;hrHeVl/SaA:$dqDTJP^3A,V+_23DFTo\_-']@e_8ej9r6-R(8,s*J?Nqf,B,,6lY^oXJMD5dC;B%#4;sg$<1CR,#:up%]aSo<9H!usp5>GYp.V^t<>VQTVp`3m-l-"B985/N\B.POB_"SBB1lp@\S?J,kNFQtosK&q:/?RU5:9"PLkg]3TP7V!-r&9PCZdgO\ne3F/`Q*fgJ(-N'n7ZNH!ItYfqf5bIt=g9q1#A7(?+Pd`dD+!hbsFojgSagm2Z#R?ti7Cj^(9'Np_$MA=%SonIN51p2g[h^B")uHgq~>endstream +endobj +xref +0 133 +0000000000 65535 f +0000000073 00000 n +0000000134 00000 n +0000000241 00000 n +0000000353 00000 n +0000000558 00000 n +0000000641 00000 n +0000000846 00000 n +0000000951 00000 n +0000001156 00000 n +0000001361 00000 n +0000001567 00000 n +0000001773 00000 n +0000001979 00000 n +0000002185 00000 n +0000002391 00000 n +0000002597 00000 n +0000002803 00000 n +0000003009 00000 n +0000003215 00000 n +0000003421 00000 n +0000003627 00000 n +0000003833 00000 n +0000004039 00000 n +0000004245 00000 n +0000004451 00000 n +0000004657 00000 n +0000004863 00000 n +0000005069 00000 n +0000005275 00000 n +0000005481 00000 n +0000005687 00000 n +0000005893 00000 n +0000006099 00000 n +0000006305 00000 n +0000006511 00000 n +0000006718 00000 n +0000006925 00000 n +0000007132 00000 n +0000007339 00000 n +0000007546 00000 n +0000007753 00000 n +0000007960 00000 n +0000008167 00000 n +0000008374 00000 n +0000008581 00000 n +0000008788 00000 n +0000008995 00000 n +0000009202 00000 n +0000009409 00000 n +0000009616 00000 n +0000009823 00000 n +0000010030 00000 n +0000010237 00000 n +0000010444 00000 n +0000010651 00000 n +0000010858 00000 n +0000011065 00000 n +0000011272 00000 n +0000011479 00000 n +0000011686 00000 n +0000011893 00000 n +0000012100 00000 n +0000012307 00000 n +0000012514 00000 n +0000012721 00000 n +0000012928 00000 n +0000013135 00000 n +0000013342 00000 n +0000013412 00000 n +0000013696 00000 n +0000014199 00000 n +0000014900 00000 n +0000016266 00000 n +0000017620 00000 n +0000018842 00000 n +0000020138 00000 n +0000021241 00000 n +0000022349 00000 n +0000023431 00000 n +0000024949 00000 n +0000026133 00000 n +0000027374 00000 n +0000028182 00000 n +0000029685 00000 n +0000030926 00000 n +0000032265 00000 n +0000032640 00000 n +0000034118 00000 n +0000035397 00000 n +0000036636 00000 n +0000037977 00000 n +0000039298 00000 n +0000040495 00000 n +0000040841 00000 n +0000042195 00000 n +0000043451 00000 n +0000044699 00000 n +0000045481 00000 n +0000047030 00000 n +0000048417 00000 n +0000049379 00000 n +0000050833 00000 n +0000052102 00000 n +0000053411 00000 n +0000053887 00000 n +0000055352 00000 n +0000056465 00000 n +0000057349 00000 n +0000058792 00000 n +0000059880 00000 n +0000060889 00000 n +0000061903 00000 n +0000063262 00000 n +0000064492 00000 n +0000065679 00000 n +0000066017 00000 n +0000067425 00000 n +0000068683 00000 n +0000069922 00000 n +0000070980 00000 n +0000071358 00000 n +0000072917 00000 n +0000074176 00000 n +0000075329 00000 n +0000075922 00000 n +0000077115 00000 n +0000078378 00000 n +0000079516 00000 n +0000080530 00000 n +0000081034 00000 n +0000082481 00000 n +0000083958 00000 n +trailer +<< +/ID +[<8fde224717b9d52e5e32f39f5f8fd2c5><8fde224717b9d52e5e32f39f5f8fd2c5>] +% ReportLab generated PDF document -- digest (http://www.reportlab.com) + +/Info 69 0 R +/Root 68 0 R +/Size 133 +>> +startxref +85091 +%%EOF diff --git a/docker-compose.yml b/docker-compose.yml index 75e6646..f75182b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -721,48 +721,47 @@ services: # ===================================== ai-analysis-service: - build: ./services/ai-analysis-service - container_name: pipeline_ai_analysis_service + build: + context: ./services/ai-analysis-service + dockerfile: Dockerfile + container_name: pipeline_ai_analysis ports: - "8022:8022" environment: - PORT=8022 - HOST=0.0.0.0 - ANTHROPIC_API_KEY=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 - - POSTGRES_HOST=postgres + + # Neo4j Configuration + - USE_NEO4J_KG=true + - NEO4J_URI=bolt://neo4j:7687 + - NEO4J_USER=neo4j + - NEO4J_PASSWORD=password + - NEO4J_DATABASE=neo4j + + # Report Configuration + - REPORT_TECHNICAL_ONLY=false + + # Existing database configurations + - POSTGRES_HOST=pipeline_postgres - POSTGRES_PORT=5432 - POSTGRES_DB=dev_pipeline - POSTGRES_USER=pipeline_admin - POSTGRES_PASSWORD=secure_pipeline_2024 - - REDIS_HOST=redis + + - MONGODB_URL=mongodb://pipeline_admin:mongo_secure_2024@pipeline_mongodb:27017/ + - MONGODB_DB=repo_analyzer + + - REDIS_HOST=pipeline_redis - REDIS_PORT=6379 - REDIS_PASSWORD=redis_secure_2024 - - MONGODB_URL=mongodb://pipeline_admin:mongo_secure_2024@mongodb:27017/ - - MONGODB_DB=repo_analyzer - - GIT_INTEGRATION_SERVICE_URL=http://git-integration:8012 - - CLAUDE_REQUESTS_PER_MINUTE=90 - - MAX_FILES_DEFAULT=100 - - CACHE_TTL_SECONDS=86400 - - CONTENT_MAX_TOKENS=8000 - - ENHANCED_PROCESSING_ENABLED=true - - ENHANCED_BATCH_PROCESSING=true - - ENHANCED_SMART_CHUNKING=true - - ENHANCED_RATE_LIMIT=120 - - ENHANCED_BATCH_DELAY=0.05 - - ENHANCED_SMALL_FILE_DELAY=0.02 - - ENHANCED_MEDIUM_FILE_DELAY=0.05 - - ENHANCED_LARGE_FILE_DELAY=0.1 - volumes: - - ai_analysis_logs:/app/logs - - ./ai-analysis-reports:/app/reports - - ai_analysis_temp:/app/temp + depends_on: + - neo4j + - postgres + - mongodb + - redis networks: - pipeline_network - depends_on: - - postgres - - redis - - mongodb - - git-integration deploy: resources: limits: diff --git a/services/ai-analysis-service/__init__.py b/services/ai-analysis-service/__init__.py new file mode 100644 index 0000000..2e29b52 --- /dev/null +++ b/services/ai-analysis-service/__init__.py @@ -0,0 +1,8 @@ +# Service initialization +# This file helps Python treat the directory as a package +# and can be used to set up any service-wide configurations + +from .server import app # Import the FastAPI app +from .ai_analyze import analyze_repository # Import key functions + +__all__ = ['app', 'analyze_repository'] diff --git a/services/ai-analysis-service/ai-analyze.py b/services/ai-analysis-service/ai-analyze.py index f141107..e178f9c 100644 --- a/services/ai-analysis-service/ai-analyze.py +++ b/services/ai-analysis-service/ai-analyze.py @@ -19,13 +19,14 @@ Example: import os import asyncio +import copy import hashlib import json import uuid from pathlib import Path from typing import Dict, List, Optional, Tuple, Any from datetime import datetime, timedelta -from dataclasses import dataclass, asdict, field +from dataclasses import dataclass, asdict, field, is_dataclass from collections import defaultdict, Counter import logging import tempfile @@ -2358,91 +2359,38 @@ Focus on business outcomes, financial impact, and competitive implications. Use ts_files = [fa for fa in direct_frontend_check if fa.path.lower().endswith(('.ts', '.tsx'))] ai_analysis_text = f""" -**1. FRONTEND OVERVIEW - WHAT IS THE FRONTEND?** +**1. FRONTEND OVERVIEW** +This repository contains {len(direct_frontend_check)} frontend-focused files totaling {actual_total_lines:,} lines of code that render the browser experience. -The frontend is the part of the application that users see and interact with in their web browser. Think of it like the visible part of an iceberg - what users see on their screen. +**2. FILE TYPE SUMMARY** +- HTML Files ({len(html_files)}): define document structure, semantics, and mount points for client-side scripts. +- CSS Files ({len(css_files)}): enforce layout rules, typography, spacing, and responsive behaviour. +- JavaScript Files ({len(js_files)}): implement interaction logic, event handling, API connectivity, and state coordination. +- TypeScript Files ({len(ts_files)}): offer typed JavaScript sources that compile into the runtime JavaScript bundle. -This repository contains {len(direct_frontend_check)} frontend files with a total of {actual_total_lines:,} lines of code that create the user interface. +**3. RUNTIME EXECUTION FLOW** +1. Browsers parse HTML and construct the DOM tree. +2. Linked CSS stylesheets apply presentation and responsive breakpoints. +3. JavaScript or TypeScript modules initialise, attach event listeners, and hydrate components. +4. State transitions trigger DOM updates through framework abstractions or direct DOM manipulation. -**2. FRONTEND FILE TYPES - WHAT EACH TYPE DOES** +**4. USER INTERACTION PIPELINE** +- Button handlers execute JavaScript callbacks that drive navigation, trigger side effects, or fetch data. +- Form flows validate input, submit HTTP requests, handle responses, and update the interface accordingly. -**HTML Files ({len(html_files)} files):** -- HTML files are like the skeleton or framework of a building -- They define WHAT appears on the page (headings, buttons, forms, text, images) -- Think of HTML as the structure - like the walls and rooms of a house -- These files create the basic layout and content structure +**5. DATA FLOW** +1. User actions invoke JavaScript functions that compose HTTP requests. +2. Backend endpoints respond with JSON or HTML payloads. +3. The frontend updates local state and re-renders affected DOM nodes. +4. CSS keeps presentation consistent after dynamic content updates. -**CSS Files ({len(css_files)} files):** -- CSS files are like the paint, decoration, and interior design -- They control HOW things look (colors, sizes, spacing, fonts, layouts) -- Think of CSS as the styling - making the house look beautiful -- These files make the page visually appealing and organized +**6. STRUCTURE AND ORGANISATION** +- Markup, styling, and behavioural concerns are separated across HTML, CSS, and script assets for maintainability. +- JavaScript modules coordinate data loading, state transitions, and rendering routines. +- Styling sources encapsulate visual design without coupling to business logic. -**JavaScript Files ({len(js_files)} files):** -- JavaScript files are like the electrical system and appliances -- They add INTERACTIVITY and FUNCTIONALITY (clicking buttons, submitting forms, loading data) -- Think of JavaScript as the "smarts" - making things work when you click them -- These files make the page dynamic and responsive to user actions - -**TypeScript Files ({len(ts_files)} files):** -- TypeScript files are enhanced JavaScript files with better error checking -- They work the same as JavaScript but with additional safety features -- Think of TypeScript as JavaScript with better quality control - -**3. HOW THE FRONTEND WORKS - STEP-BY-STEP EXPLANATION** - -**Step 1: Loading the Page** -When a user opens the website, the browser reads the HTML file first. This tells the browser what elements to display (like a blueprint tells builders what to build). - -**Step 2: Styling the Page** -Next, the browser reads the CSS files. These tell the browser how to style each element - what colors to use, how big things should be, where to place them (like interior designers telling builders how to decorate). - -**Step 3: Making It Interactive** -Finally, the browser runs the JavaScript/TypeScript files. These add the "brain" - making buttons clickable, forms submittable, and data loadable (like installing electrical systems and appliances). - -**4. USER INTERACTION FLOW** - -**When a User Clicks a Button:** -1. The HTML defines where the button is -2. The CSS makes it look like a button (colored, styled) -3. The JavaScript detects the click -4. The JavaScript performs the action (like sending data to the server) -5. The page updates to show the result - -**When a User Fills a Form:** -1. The HTML creates the form structure (input fields, labels) -2. The CSS styles the form (makes it look nice) -3. The JavaScript validates the input (checks if it's correct) -4. The JavaScript sends the data to the server -5. The page shows a success or error message - -**5. DATA FLOW - HOW INFORMATION MOVES** - -**Getting Data from Server:** -1. User clicks a button or loads a page -2. JavaScript sends a request to the server (like ordering food) -3. Server processes the request and sends back data (like the kitchen preparing food) -4. JavaScript receives the data (like receiving the food) -5. JavaScript updates the HTML to show the data (like displaying it on the plate) -6. CSS styles the data display (like arranging the food nicely) - -**6. STRUCTURE AND ORGANIZATION** - -The frontend files are organized in a way that makes them easy to maintain: -- HTML files define the structure -- CSS files control the appearance -- JavaScript files add the functionality - -They all work together like parts of a machine - each part has a specific job, but they all need to work together for the machine to function properly. - -**7. FRONTEND ARCHITECTURE SUMMARY** - -This frontend uses a traditional web architecture: -- HTML provides the foundation (structure) -- CSS provides the styling (appearance) -- JavaScript provides the behavior (functionality) - -Together, these files create a complete, interactive web application that users can see, use, and interact with in their web browsers. +**7. SUMMARY** +This fallback section originates from static inspection because AI synthesis was unavailable. Re-run the analysis with the AI service enabled for a richer narrative tied to code evidence. """ if ai_analysis_text: @@ -3870,6 +3818,46 @@ modelBuilder.Entity() borderColor=colors.HexColor('#d1d5db'), borderPadding=6 ) + + def _is_truthy(value: Optional[str], default: bool = False) -> bool: + if value is None: + return default + return str(value).strip().lower() in ("1", "true", "yes", "on") + + technical_only = _is_truthy(os.getenv("REPORT_TECHNICAL_ONLY"), default=True) + self._report_technical_only = technical_only + include_nontechnical = not technical_only + + def append_markdown_block(markdown_text: str, paragraph_style: ParagraphStyle) -> bool: + if not markdown_text or not markdown_text.strip(): + return False + elements = self._convert_markdown_to_pdf_elements( + markdown_text, + styles, + section_style, + subsection_style, + code_style, + paragraph_style + ) + story.extend(elements) + return True + + def render_markdown_pair( + nontech_md: str, + tech_md: str, + spacer_after_nontech: float = 10, + add_page_break: bool = False + ) -> None: + section_has_content = False + if include_nontechnical: + if append_markdown_block(nontech_md, nontech_style): + section_has_content = True + if spacer_after_nontech: + story.append(Spacer(1, spacer_after_nontech)) + if append_markdown_block(tech_md, tech_style): + section_has_content = True + if add_page_break and section_has_content: + story.append(PageBreak()) # Extract context data module_analyses = comprehensive_context.get('module_analyses', []) @@ -3919,18 +3907,12 @@ modelBuilder.Entity() progress_mgr=progress_mgr ) - # Convert markdown to properly formatted PDF elements - exec_summary_elements = self._convert_markdown_to_pdf_elements( - exec_summary_nontech, styles, section_style, subsection_style, code_style, nontech_style + render_markdown_pair( + exec_summary_nontech, + exec_summary_tech, + spacer_after_nontech=10, + add_page_break=True ) - story.extend(exec_summary_elements) - story.append(Spacer(1, 10)) - - exec_summary_tech_elements = self._convert_markdown_to_pdf_elements( - exec_summary_tech, styles, section_style, subsection_style, code_style, tech_style - ) - story.extend(exec_summary_tech_elements) - story.append(PageBreak()) # SECTION 3: PROJECT OVERVIEW (Multi-Level) if progress_mgr: @@ -3951,18 +3933,12 @@ modelBuilder.Entity() progress_mgr=progress_mgr ) - # Convert markdown to properly formatted PDF elements - project_overview_elements = self._convert_markdown_to_pdf_elements( - project_overview_nontech, styles, section_style, subsection_style, code_style, nontech_style + render_markdown_pair( + project_overview_nontech, + project_overview_tech, + spacer_after_nontech=15, + add_page_break=True ) - story.extend(project_overview_elements) - story.append(Spacer(1, 15)) - - project_overview_tech_elements = self._convert_markdown_to_pdf_elements( - project_overview_tech, styles, section_style, subsection_style, code_style, tech_style - ) - story.extend(project_overview_tech_elements) - story.append(PageBreak()) # SECTION 4: ARCHITECTURE ANALYSIS (Multi-Level with Frontend, Backend, Database, APIs) print(f" šŸ“ SECTION 3: ARCHITECTURE ANALYSIS") @@ -3984,18 +3960,12 @@ modelBuilder.Entity() synthesis_analysis=synthesis_analysis, progress_mgr=progress_mgr ) - # Convert markdown to properly formatted PDF elements - frontend_elements = self._convert_markdown_to_pdf_elements( - frontend_nontech, styles, section_style, subsection_style, code_style, nontech_style + render_markdown_pair( + frontend_nontech, + frontend_tech, + spacer_after_nontech=10, + add_page_break=True ) - story.extend(frontend_elements) - story.append(Spacer(1, 10)) - - frontend_tech_elements = self._convert_markdown_to_pdf_elements( - frontend_tech, styles, section_style, subsection_style, code_style, tech_style - ) - story.extend(frontend_tech_elements) - story.append(PageBreak()) # 4.2 Backend Architecture story.append(Paragraph("3.2 Backend Architecture", subsection_style)) @@ -4007,18 +3977,12 @@ modelBuilder.Entity() synthesis_analysis=synthesis_analysis, progress_mgr=progress_mgr ) - # Convert markdown to properly formatted PDF elements - backend_elements = self._convert_markdown_to_pdf_elements( - backend_nontech, styles, section_style, subsection_style, code_style, nontech_style + render_markdown_pair( + backend_nontech, + backend_tech, + spacer_after_nontech=10, + add_page_break=True ) - story.extend(backend_elements) - story.append(Spacer(1, 10)) - - backend_tech_elements = self._convert_markdown_to_pdf_elements( - backend_tech, styles, section_style, subsection_style, code_style, tech_style - ) - story.extend(backend_tech_elements) - story.append(PageBreak()) # 4.3 Database Architecture story.append(Paragraph("3.3 Database Architecture", subsection_style)) @@ -4030,18 +3994,12 @@ modelBuilder.Entity() synthesis_analysis=synthesis_analysis, progress_mgr=progress_mgr ) - # Convert markdown to properly formatted PDF elements - database_elements = self._convert_markdown_to_pdf_elements( - database_nontech, styles, section_style, subsection_style, code_style, nontech_style + render_markdown_pair( + database_nontech, + database_tech, + spacer_after_nontech=10, + add_page_break=True ) - story.extend(database_elements) - story.append(Spacer(1, 10)) - - database_tech_elements = self._convert_markdown_to_pdf_elements( - database_tech, styles, section_style, subsection_style, code_style, tech_style - ) - story.extend(database_tech_elements) - story.append(PageBreak()) # 4.4 API Architecture story.append(Paragraph("3.4 API Architecture", subsection_style)) @@ -4053,18 +4011,12 @@ modelBuilder.Entity() synthesis_analysis=synthesis_analysis, progress_mgr=progress_mgr ) - # Convert markdown to properly formatted PDF elements - api_elements = self._convert_markdown_to_pdf_elements( - api_nontech, styles, section_style, subsection_style, code_style, nontech_style + render_markdown_pair( + api_nontech, + api_tech, + spacer_after_nontech=10, + add_page_break=True ) - story.extend(api_elements) - story.append(Spacer(1, 10)) - - api_tech_elements = self._convert_markdown_to_pdf_elements( - api_tech, styles, section_style, subsection_style, code_style, tech_style - ) - story.extend(api_tech_elements) - story.append(PageBreak()) # SECTION 5: SECURITY ASSESSMENT (Multi-Level) print(f" šŸ“ SECTION 4: SECURITY ASSESSMENT") @@ -4087,18 +4039,12 @@ modelBuilder.Entity() progress_mgr=progress_mgr ) - # Convert markdown to properly formatted PDF elements - security_elements = self._convert_markdown_to_pdf_elements( - security_nontech, styles, section_style, subsection_style, code_style, nontech_style + render_markdown_pair( + security_nontech, + security_tech, + spacer_after_nontech=15, + add_page_break=True ) - story.extend(security_elements) - story.append(Spacer(1, 15)) - - security_tech_elements = self._convert_markdown_to_pdf_elements( - security_tech, styles, section_style, subsection_style, code_style, tech_style - ) - story.extend(security_tech_elements) - story.append(PageBreak()) # SECTION 6: MODULE DEEP DIVES (One per module) print(f" šŸ“ SECTION 5: MODULE DEEP DIVES") @@ -4168,18 +4114,12 @@ modelBuilder.Entity() progress_mgr=progress_mgr ) - # Convert markdown to properly formatted PDF elements - module_elements = self._convert_markdown_to_pdf_elements( - module_nontech, styles, section_style, subsection_style, code_style, nontech_style + render_markdown_pair( + module_nontech, + module_tech, + spacer_after_nontech=10, + add_page_break=True ) - story.extend(module_elements) - story.append(Spacer(1, 10)) - - module_tech_elements = self._convert_markdown_to_pdf_elements( - module_tech, styles, section_style, subsection_style, code_style, tech_style - ) - story.extend(module_tech_elements) - story.append(PageBreak()) else: # No file analyses either - generate minimal section story.append(Paragraph("No modules found in analysis. Please check the analysis logs.", tech_style)) @@ -4203,18 +4143,12 @@ modelBuilder.Entity() progress_mgr=progress_mgr ) - # Convert markdown to properly formatted PDF elements - module_elements = self._convert_markdown_to_pdf_elements( - module_nontech, styles, section_style, subsection_style, code_style, nontech_style + render_markdown_pair( + module_nontech, + module_tech, + spacer_after_nontech=10, + add_page_break=True ) - story.extend(module_elements) - story.append(Spacer(1, 10)) - - module_tech_elements = self._convert_markdown_to_pdf_elements( - module_tech, styles, section_style, subsection_style, code_style, tech_style - ) - story.extend(module_tech_elements) - story.append(PageBreak()) # SECTION 7: CRITICAL ISSUES & RECOMMENDATIONS (Multi-Level) print(f" šŸ“ SECTION 6: CRITICAL ISSUES & RECOMMENDATIONS") @@ -4236,67 +4170,15 @@ modelBuilder.Entity() progress_mgr=progress_mgr ) - # Convert markdown to properly formatted PDF elements - issues_elements = self._convert_markdown_to_pdf_elements( - issues_nontech, styles, section_style, subsection_style, code_style, nontech_style + render_markdown_pair( + issues_nontech, + issues_tech, + spacer_after_nontech=15, + add_page_break=True ) - story.extend(issues_elements) - story.append(Spacer(1, 15)) - issues_tech_elements = self._convert_markdown_to_pdf_elements( - issues_tech, styles, section_style, subsection_style, code_style, tech_style - ) - story.extend(issues_tech_elements) - story.append(PageBreak()) - - # SECTION 7: CODE EVIDENCE & PROOF (NEW) - print(f" šŸ“ SECTION 7: CODE EVIDENCE & PROOF") - if progress_mgr: - await progress_mgr.emit_event("report_progress", { - "message": "Generating code evidence section", - "percent": 78 - }) - - story.append(Paragraph("SECTION 7: CODE EVIDENCE & PROOF", section_style)) - - # Get all file analyses from comprehensive context - all_file_analyses = [] - if 'file_analyses' in comprehensive_context: - all_file_analyses = comprehensive_context['file_analyses'] - elif module_analyses: - # Extract file analyses from module analyses - for module in module_analyses: - if 'file_analyses' in module: - all_file_analyses.extend(module['file_analyses']) - - # Extract code evidence - code_evidence = self._extract_code_evidence_for_report(all_file_analyses) - - if code_evidence: - # Generate code evidence sections - evidence_nontech, evidence_tech = await self._generate_code_evidence_section( - code_evidence=code_evidence, - progress_mgr=progress_mgr - ) - - # Convert markdown to properly formatted PDF elements - evidence_elements = self._convert_markdown_to_pdf_elements( - evidence_nontech, styles, section_style, subsection_style, code_style, nontech_style - ) - story.extend(evidence_elements) - story.append(Spacer(1, 15)) - - evidence_tech_elements = self._convert_markdown_to_pdf_elements( - evidence_tech, styles, section_style, subsection_style, code_style, tech_style - ) - story.extend(evidence_tech_elements) - else: - story.append(Paragraph("No specific code evidence available. File analyses may not contain detailed issue information.", tech_style)) - - story.append(PageBreak()) - - # SECTION 8: SYSTEM-LEVEL INSIGHTS (Multi-Level) - print(f" šŸ“ SECTION 8: SYSTEM-LEVEL INSIGHTS") + # SECTION 7: SYSTEM-LEVEL INSIGHTS (Multi-Level) + print(f" šŸ“ SECTION 7: SYSTEM-LEVEL INSIGHTS") if progress_mgr: await progress_mgr.emit_event("report_progress", { "message": "Generating system-level insights", @@ -4315,18 +4197,12 @@ modelBuilder.Entity() progress_mgr=progress_mgr ) - # Convert markdown to properly formatted PDF elements - system_elements = self._convert_markdown_to_pdf_elements( - system_nontech, styles, section_style, subsection_style, code_style, nontech_style + render_markdown_pair( + system_nontech, + system_tech, + spacer_after_nontech=15, + add_page_break=True ) - story.extend(system_elements) - story.append(Spacer(1, 15)) - - system_tech_elements = self._convert_markdown_to_pdf_elements( - system_tech, styles, section_style, subsection_style, code_style, tech_style - ) - story.extend(system_tech_elements) - story.append(PageBreak()) # SECTION 9: JUNIOR DEVELOPER ONBOARDING GUIDE (Technical Only) if progress_mgr: @@ -4370,17 +4246,12 @@ modelBuilder.Entity() progress_mgr=progress_mgr ) - # Convert markdown to properly formatted PDF elements - conclusion_elements = self._convert_markdown_to_pdf_elements( - conclusion_nontech, styles, section_style, subsection_style, code_style, nontech_style + render_markdown_pair( + conclusion_nontech, + conclusion_tech, + spacer_after_nontech=15, + add_page_break=False ) - story.extend(conclusion_elements) - story.append(Spacer(1, 15)) - - conclusion_tech_elements = self._convert_markdown_to_pdf_elements( - conclusion_tech, styles, section_style, subsection_style, code_style, tech_style - ) - story.extend(conclusion_tech_elements) # Build PDF try: @@ -4406,6 +4277,132 @@ modelBuilder.Entity() print(f" Output Path: {output_path}") print(f"{'='*80}\n") raise + finally: + self._report_technical_only = None + + def _prepare_llm_payload(self, section_name: str, section_data: Dict[str, Any]) -> Dict[str, Any]: + """Trim large fields from section data to keep prompts below token limits.""" + if not isinstance(section_data, dict): + return section_data + + safe_payload = copy.deepcopy(section_data) + + modules = safe_payload.get("module_analyses") + if isinstance(modules, list): + max_modules = 8 if section_name.lower().startswith("module") else 12 + safe_payload["module_analyses"] = modules[:max_modules] + + allowed_module_names = { + m.get("module_name") + for m in safe_payload.get("module_analyses", []) + if isinstance(m, dict) + } + + metrics_by_module = safe_payload.get("metrics_by_module") + if isinstance(metrics_by_module, dict) and allowed_module_names: + safe_payload["metrics_by_module"] = { + name: metrics_by_module[name] + for name in allowed_module_names + if name in metrics_by_module + } + + findings_by_module = safe_payload.get("findings_by_module") + if isinstance(findings_by_module, dict) and allowed_module_names: + safe_payload["findings_by_module"] = { + name: findings_by_module[name] + for name in allowed_module_names + if name in findings_by_module + } + + max_string_length = 1500 + max_top_list = 20 + max_nested_list = 10 + max_dict_items = 40 + + exclude_exact = { + "detailed_analysis", + "full_response", + "raw_content", + "raw_text", + "raw_chunks", + "content", + "code", + "code_snippet", + "file_content", + "nontechnical_version", + "technical_version", + "non_technical_version", + "markdown", + "html", + "prompt", + "raw_prompt", + "claude_response", + "pdf_data", + } + exclude_substrings = [ + "raw_", + "_raw", + "nontechnical", + "non-technical", + "technical_version", + "_html", + "markdown", + "html", + "prompt", + "claude", + ] + + def truncate(value: Any, depth: int = 0) -> Any: + if is_dataclass(value): + value = asdict(value) + + if isinstance(value, str): + if len(value) > max_string_length: + return ( + value[:max_string_length] + + f"... [truncated {len(value) - max_string_length} chars]" + ) + return value + + if isinstance(value, (int, float, bool)) or value is None: + return value + + if isinstance(value, list): + limit = max_top_list if depth == 0 else max_nested_list + trimmed = value[:limit] + result = [truncate(item, depth + 1) for item in trimmed] + if len(value) > limit: + result.append( + f"... {len(value) - limit} additional items truncated ..." + ) + return result + + if isinstance(value, dict): + result = {} + items = list(value.items()) + truncated_count = 0 + if len(items) > max_dict_items: + truncated_count = len(items) - max_dict_items + items = items[:max_dict_items] + + for key, item in items: + key_str = str(key) + if key_str in exclude_exact: + continue + key_lower = key_str.lower() + if any(sub in key_lower for sub in exclude_substrings): + continue + result[key] = truncate(item, depth + 1) + + if truncated_count: + result["__truncated_keys__"] = ( + f"{truncated_count} additional keys removed" + ) + return result + + return str(value) + + return truncate(safe_payload) async def _generate_section_multi_level( self, @@ -4418,21 +4415,45 @@ modelBuilder.Entity() Returns: (non_technical_version, technical_version) """ try: - prompt = f""" + technical_only_default = True + section_technical_only = getattr(self, "_report_technical_only", None) + if section_technical_only is None: + technical_only_env = os.getenv("REPORT_TECHNICAL_ONLY") + if technical_only_env is None: + section_technical_only = technical_only_default + else: + section_technical_only = technical_only_env.lower() in ("1", "true", "yes", "on") + + sanitized_section_data = self._prepare_llm_payload(section_name, section_data) + + if section_technical_only: + prompt = f""" +You are a senior software architect with 30+ years of experience. Produce a strictly technical analysis for the section "{section_name}". + +CONSTRAINTS: +- Focus exclusively on code, architecture, dependencies, security, performance, and testing. +- Do not include business analogies, executive summaries, emojis, or non-technical commentary. +- Reference files, modules, and metrics when possible. +- Respond in GitHub-flavoured Markdown with clear headings and bullet lists. + +SECTION DATA: +{json.dumps(sanitized_section_data, indent=2, default=str)} +""" + else: + prompt = f""" You are a senior software architect with 30+ years of experience. Generate a comprehensive analysis for the section: "{section_name}". SECTION DATA: -{json.dumps(section_data, indent=2, default=str)} +{json.dumps(sanitized_section_data, indent=2, default=str)} Generate TWO versions of this section: 1. NON-TECHNICAL VERSION: - - Use analogies (restaurant, building, car, city) - - No jargon - explain in plain English - - Focus on business impact and implications - - Use emojis for ratings (⭐⭐⭐ for good, āš ļø for warnings, āŒ for critical) - - Explain what this means for stakeholders - - Keep it accessible to executives and non-technical managers + - Use clear, plain language without analogies, metaphors, or storytelling. + - Avoid jargon while grounding statements in observable evidence. + - Focus on business impact and stakeholder implications derived from the section data. + - Do not use emojis or decorative symbols. + - Keep the structure concise and easy for executives to follow. 2. TECHNICAL VERSION: - Full technical details with code examples @@ -4463,6 +4484,12 @@ Output format: response_text = await loop.run_in_executor(None, call_claude) + if section_technical_only: + technical = response_text.strip() + if not technical: + technical = "Technical analysis generation failed." + return "", technical + # Parse response nontech_match = re.search(r'\[NON-TECHNICAL\](.*?)(?=\[TECHNICAL\]|$)', response_text, re.DOTALL) tech_match = re.search(r'\[TECHNICAL\](.*?)$', response_text, re.DOTALL) @@ -4819,6 +4846,59 @@ Output format: try: if not code_evidence: return "No specific code evidence found.", "No code evidence available." + + technical_only_default = True + section_technical_only = getattr(self, "_report_technical_only", None) + if section_technical_only is None: + technical_only_env = os.getenv("REPORT_TECHNICAL_ONLY") + if technical_only_env is None: + section_technical_only = technical_only_default + else: + section_technical_only = technical_only_env.lower() in ("1", "true", "yes", "on") + + high_count = len([e for e in code_evidence if e['severity'] == 'HIGH']) + medium_count = len([e for e in code_evidence if e['severity'] == 'MEDIUM']) + unique_files = len(set(e['file'] for e in code_evidence)) + + tech_content = f""" + šŸ”§ DETAILED CODE EVIDENCE ANALYSIS

+ + šŸ“‹ COMPREHENSIVE FINDINGS:
+ Total Issues Found: {len(code_evidence)}
+ Files Analyzed: {unique_files}
+ High Severity: {high_count}
+ Medium Severity: {medium_count}

+ """ + + for idx, evidence in enumerate(code_evidence[:10], 1): # Top 10 detailed findings + tech_content += f""" + FINDING #{idx} - {evidence['severity']} PRIORITY
+ File: {evidence['file']}
+ Issue: {evidence['issue']}
+ Line: {evidence['line_number']}

+ + Code Evidence:
+
+{evidence['code_snippet'][:300]}{"..." if len(evidence['code_snippet']) > 300 else ""}
+                
+ + Recommended Fix:
+ {evidence['recommendation']}

+ + {'─' * 60}
+ """ + + tech_content += """ + šŸ”§ IMPLEMENTATION NOTES:
+ • Focus on HIGH severity issues first
+ • Test all fixes in staging environment
+ • Use code review process for all changes
+ • Update documentation after fixes
+ • Consider automated testing for fixed issues + """ + + if section_technical_only: + return "", tech_content # Non-technical version (for managers) nontech_content = f""" @@ -4827,9 +4907,9 @@ Output format: Our automated code review identified {len(code_evidence)} specific issues with actual code examples as proof.

šŸ“Š ISSUE BREAKDOWN:
- • High Priority Issues: {len([e for e in code_evidence if e['severity'] == 'HIGH'])}
- • Medium Priority Issues: {len([e for e in code_evidence if e['severity'] == 'MEDIUM'])}
- • Files with Evidence: {len(set(e['file'] for e in code_evidence))}

+ • High Priority Issues: {high_count}
+ • Medium Priority Issues: {medium_count}
+ • Files with Evidence: {unique_files}

šŸŽÆ TOP CRITICAL FINDINGS:
""" @@ -4853,46 +4933,6 @@ Output format: ⚔ IMMEDIATE ACTION:
Assign developers to fix high-priority issues within 1-2 weeks to prevent system degradation. """ - - # Technical version (for developers) - tech_content = f""" - šŸ”§ DETAILED CODE EVIDENCE ANALYSIS

- - šŸ“‹ COMPREHENSIVE FINDINGS:
- Total Issues Found: {len(code_evidence)}
- Files Analyzed: {len(set(e['file'] for e in code_evidence))}
- High Severity: {len([e for e in code_evidence if e['severity'] == 'HIGH'])}
- Medium Severity: {len([e for e in code_evidence if e['severity'] == 'MEDIUM'])}

- """ - - # Add detailed code evidence for developers - for idx, evidence in enumerate(code_evidence[:10], 1): # Top 10 detailed findings - tech_content += f""" - FINDING #{idx} - {evidence['severity']} PRIORITY
- File: {evidence['file']}
- Issue: {evidence['issue']}
- Line: {evidence['line_number']}

- - Code Evidence:
-
-{evidence['code_snippet'][:300]}{"..." if len(evidence['code_snippet']) > 300 else ""}
-                
- - Recommended Fix:
- {evidence['recommendation']}

- - {'─' * 60}
- """ - - tech_content += """ - šŸ”§ IMPLEMENTATION NOTES:
- • Focus on HIGH severity issues first
- • Test all fixes in staging environment
- • Use code review process for all changes
- • Update documentation after fixes
- • Consider automated testing for fixed issues - """ - return nontech_content, tech_content except Exception as e: @@ -7054,18 +7094,23 @@ async def main(): js_files = [fa for fa in frontend_files if fa.path.lower().endswith(('.js', '.jsx', '.mjs', '.cjs'))] ts_files = [fa for fa in frontend_files if fa.path.lower().endswith(('.ts', '.tsx'))] - frontend_analysis_prompt = f""" -You are a Senior Frontend Architect and Technical Writer with 20+ years of experience. Your task is to analyze this frontend codebase and create a COMPREHENSIVE, DETAILED explanation that even a non-technical person can understand. + front_end_prompt = f""" +You are a Senior Frontend Architect and Technical Writer with 20+ years of experience. Analyze this frontend codebase and produce a comprehensive, technically precise report. The audience includes senior engineers and stakeholders who expect evidence-based, objective findings. -CRITICAL: Write in SIMPLE, CLEAR language. Use analogies and real-world examples. Avoid jargon. Explain everything as if talking to someone who has never coded before. +STRICT STYLE RULES: +- Use professional, technical language only. Do not use analogies, metaphors, storytelling, or colloquial comparisons. +- Ground every statement in repository evidence. Reference file paths, components, functions, metrics, or configurations explicitly. +- When assumptions are necessary, label them clearly and mark for validation. +- Follow the outline exactly; preserve headings, numbering, and bullet structure. +- Provide 2000-3000 words of detailed insight. FRONTEND FILES SUMMARY: - Total Frontend Files: {len(frontend_files)} - Total Frontend Lines: {sum(fa.lines_of_code for fa in frontend_files):,} -- HTML Files: {len(html_files)} files -- CSS/Styling Files: {len(css_files)} files -- JavaScript Files: {len(js_files)} files -- TypeScript Files: {len(ts_files)} files +- HTML Files: {len(html_files)} +- CSS/Styling Files: {len(css_files)} +- JavaScript Files: {len(js_files)} +- TypeScript Files: {len(ts_files)} - Component Files: {len(component_files)} - Routing Files: {len(routing_files)} - State Management Files: {len(state_files)} @@ -7085,159 +7130,85 @@ STATE MANAGEMENT FILES: SAMPLE FRONTEND FILES: {frontend_files_text} -Provide a COMPREHENSIVE and EXTREMELY DETAILED frontend architecture analysis following this EXACT structure. Write at least 2000-3000 words. Be very thorough and detailed: +Produce the analysis using this structure: **1. FRONTEND FRAMEWORK DETECTION:** -- Identify the frontend framework(s) used (React, Vue, Angular, Svelte, Next.js, Nuxt, Remix, Astro, Qwik, SolidJS, Ember, Backbone, or vanilla JS) -- Detect framework versions from package.json or config files -- Identify any meta-frameworks (Next.js, Nuxt, Remix, etc.) -- Note any version-specific issues or outdated versions +- Identify framework(s) and versions (from package.json, lockfiles, config files). +- Note meta-framework usage (Next.js, Nuxt, Remix, etc.). +- Highlight version-specific issues or EOL dependencies. **2. TECHNOLOGY STACK ANALYSIS:** -- List all frontend dependencies and their purposes -- Identify any outdated or vulnerable dependencies -- Detect duplicate libraries (e.g., multiple date libraries, multiple state management libraries) -- Security issues in dependencies -- Build tools detected (Webpack, Vite, Rollup, Parcel, etc.) -- Testing frameworks and tools +- Catalogue all dependencies with purpose and usage context. +- Flag outdated/vulnerable packages (cite version/date where possible). +- Identify redundant or overlapping libraries. +- Summarize build tooling, testing frameworks, bundlers. **3. COMPONENT ARCHITECTURE (GRANULAR ANALYSIS):** -For each major component identified, provide: -- Component name and purpose (what it does in simple terms) -- Component location and file path -- Props/inputs it receives -- State it manages internally -- Dependencies on other components -- Side effects (API calls, browser storage, etc.) -- Component hierarchy (parent-child relationships) -- Component reusability assessment +For each major component: +- Name, responsibility, and file path. +- Props/inputs, outputs, and state managed. +- Dependencies and side effects (API calls, browser APIs, storage). +- Hierarchy position (parent/child relationships) and reusability assessment. **4. NAVIGATION & ROUTING ANALYSIS:** -- Routing system used (React Router, Vue Router, Angular Router, Next.js file-based routing, etc.) -- All route definitions and their purposes -- Navigation flow (how users move between pages) -- Route mapping (URL → Component mapping) -- Route guards or middleware (authentication, authorization) -- Dynamic routes and parameters -- Route structure and organization +- Routing mechanism and configuration files. +- Route definitions, dynamic parameters, guards/middleware. +- Navigation flow between major views. +- Lazy loading/split points tied to routing. **5. STATE MANAGEMENT ANALYSIS:** -- State management pattern used (Context API, Redux, Zustand, Pinia, Vuex, NgRx, MobX, Jotai, Recoil, etc.) -- Global state vs local state strategy -- State flow diagram description (how data flows through the app) -- State management issues or improvements needed -- Data fetching patterns (React Query, SWR, Apollo, etc.) +- State management paradigm (Context, Redux, Zustand, etc.). +- Global vs local state boundaries. +- Data flow patterns, caching strategies, synchronization points. +- Known issues or improvement opportunities. -**6. FRONTEND ARCHITECTURE FLOW (NON-CODER FRIENDLY - VERY DETAILED):** -This is the MOST IMPORTANT section. Explain in EXTREMELY SIMPLE language that a non-technical person can understand: +**6. FRONTEND ARCHITECTURE FLOW (TECHNICAL WALKTHROUGH):** 6.1. WHAT IS THE FRONTEND? -- Explain what frontend means in simple terms (like the part of a website users see and interact with) -- What files make up the frontend in this repository (HTML, CSS, JavaScript) -- How these files work together (like ingredients in a recipe) +- Define the UI layer in this repository. +- Enumerate file types and roles (HTML templating, styling, interactivity). -6.2. HOW DOES THE FRONTEND WORK? (STEP-BY-STEP EXPLANATION) -- User Action Flow: When a user clicks a button or types something, explain step-by-step what happens: - * What happens when the user clicks? - * Which file handles the click? - * How does the page respond? - * What information is sent to the server (if any)? - * How does the page update? -- Component Interaction: Explain how different parts of the website communicate: - * Like how different departments in a company work together - * Which parts talk to each other? - * How do they share information? -- Data Flow: Explain how data moves through the system: - * Where does data come from? (user, server, database) - * How does it travel through the frontend? - * Where does it get stored temporarily? - * How does it appear on the screen? - * Use a simple analogy like a postal system or delivery service -- Navigation Flow: Explain how users move between pages: - * How does clicking a link work? - * What happens when you go to a new page? - * How does the browser know which page to show? - * Use an analogy like navigating a building with different rooms -- State Updates: Explain when and how the screen updates: - * What triggers a screen update? - * How quickly does it update? - * What information changes on the screen? - * Use an analogy like updating a dashboard or scoreboard +6.2. HOW DOES THE FRONTEND WORK? (STEP-BY-STEP FLOW) +- User Action Flow: trace representative interactions through handlers, state updates, and rendering. +- Component Interaction: describe data exchange, context usage, dependencies. +- Data Flow: document sources (APIs, constants), transformation, caching, rendering. +- Navigation Flow: explain route resolution, dynamic segments, transition handling. +- State Updates: detail triggers, asynchronous coordination, batching/debouncing behavior. -6.3. VISUAL MAPPING (IN WORDS) -- Describe the structure like a map of a building: - * What are the main "rooms" (pages)? - * How are they connected? - * What's in each room? -- Describe the component hierarchy like a family tree: - * Which components are parents? - * Which are children? - * How do they relate to each other? +6.3. VISUAL MAPPING (TEXTUAL DESCRIPTION) +- Provide a textual map of main views/pages and relationships. +- Describe component hierarchy like a dependency graph (parent/child/data links). -6.4. REAL-WORLD ANALOGY -- Compare the frontend to something familiar: - * Like a restaurant (HTML = menu, CSS = decoration, JavaScript = waiters) - * Or a car (HTML = body, CSS = paint, JavaScript = engine) - * Or a house (HTML = structure, CSS = interior design, JavaScript = electrical system) +6.4. TECHNICAL SUMMARY +- Summarize strengths, weaknesses, and priorities in concise technical terms only. -**7. FRONTEND PERFORMANCE ANALYSIS:** -- Bundle size analysis and optimization opportunities -- Code splitting strategy -- Lazy loading implementation -- Image optimization -- Performance bottlenecks -- Memory usage concerns -- Load time estimation +**7. PERFORMANCE ANALYSIS:** +- Bundle size estimates (computed or inferred). +- Code splitting/lazy loading evaluation. +- Runtime performance considerations (render hotspots, expensive operations). +- Optimization opportunities with expected impact. -**8. FRONTEND TESTING ANALYSIS:** -- Test files found and their coverage -- Testing frameworks used -- Test coverage percentage -- Missing test areas -- Testing best practices adherence +**8. UI/UX REVIEW (STRUCTURED OBSERVATIONS):** +- Layout architecture, design system usage, spacing/typography consistency. +- Accessibility findings (ARIA usage, keyboard navigation, color contrast). +- Responsiveness across breakpoints/devices. +- Usability issues grounded in code evidence. -**9. CODE QUALITY & ISSUES:** -- Large files (>500 lines) that need refactoring -- Code duplication issues -- Component complexity issues -- Security vulnerabilities in frontend code -- Best practices violations +**9. RISK & MATURITY ASSESSMENT:** +- Architecture risk score (1-10) with technical justification. +- Maintainability risk score (comment on complexity, coupling). +- Business risk commentary (security exposure, performance degradation, compliance concerns). +- Identify technical debt hotspots with file/component references. -**10. RECOMMENDATIONS:** -- Top 5-10 specific improvements needed -- Priority order for changes -- Framework upgrade recommendations -- Architecture improvements -- Performance optimizations +**10. PRIORITIZED RECOMMENDATIONS:** +- List improvements grouped into short-term (next sprint), medium-term (1-3 months), long-term (3-6 months). +- For each recommendation provide action, expected outcome, and risk mitigated. -IMPORTANT: -- Write in clear, simple language that non-coders can understand -- Use specific examples from the codebase -- Be detailed and comprehensive -- No hardcoded or generic responses - analyze the actual code provided -- Focus on actionable insights - -Keep the response comprehensive but well-structured. Use markdown formatting for better readability. - -CRITICAL REQUIREMENTS: -1. Write at least 2000-3000 words total -2. Use simple analogies throughout (like explaining to a child or a friend who has never coded) -3. Explain EVERY technical term in simple language when first used -4. Use real-world examples and comparisons -5. Break down complex concepts into step-by-step explanations -6. Use bullet points and numbered lists for clarity -7. Include specific examples from the actual codebase provided -8. Make it so clear that even a business person can understand how the frontend works -9. Be extremely detailed - don't skip any important aspect -10. Use visual descriptions where helpful (like describing a building, a restaurant, a car, etc.) - -Remember: The goal is to make a non-technical person understand: -- What frontend files exist in this repository -- How they work together -- How the frontend functions from a user's perspective -- How data flows through the system -- How users interact with the frontend -- The complete architecture in simple terms +FINAL REQUIREMENTS: +- Keep tone technical and objective; no analogies, metaphors, or storytelling. +- Use markdown formatting as requested (bold headings, bullet lists). +- Cite file paths, components, and metrics wherever possible. +- Ensure total length between 2000-3000 words. """ try: @@ -7249,7 +7220,7 @@ Remember: The goal is to make a non-technical person understand: model=os.getenv("CLAUDE_MODEL", "claude-3-5-haiku-latest"), max_tokens=8000, # Increased from 6000 to 8000 for more detailed analysis temperature=0.1, - messages=[{"role": "user", "content": frontend_analysis_prompt}] + messages=[{"role": "user", "content": front_end_prompt}] ) ai_analysis = message.content[0].text.strip() @@ -7259,7 +7230,7 @@ Remember: The goal is to make a non-technical person understand: if not ai_analysis or len(ai_analysis) < 100: print("āš ļø [FRONTEND AI] AI analysis too short, regenerating...") # Retry with more emphasis on detail - retry_prompt = frontend_analysis_prompt + "\n\nIMPORTANT: Provide a VERY DETAILED analysis. The previous response was too short. Please provide at least 2000 words of detailed explanation." + retry_prompt = front_end_prompt + "\n\nIMPORTANT: Provide a VERY DETAILED analysis. The previous response was too short. Please provide at least 2000 words of detailed explanation." message = self.client.messages.create( model=os.getenv("CLAUDE_MODEL", "claude-3-5-haiku-latest"), max_tokens=8000, @@ -7301,7 +7272,12 @@ Remember: The goal is to make a non-technical person understand: try: # Create a simpler, more focused prompt that's more likely to succeed simple_prompt = f""" -You are explaining a frontend codebase to a non-technical person. Be VERY DETAILED and use simple language. +You are a senior frontend engineer. The previous request failed; provide a concise but technically accurate overview of this frontend codebase. + +STYLE: +- Use professional technical language only. Do not use analogies, metaphors, or storytelling. +- Reference specific file paths, components, and observable behavior from the repository. +- Mark any assumptions clearly as assumptions. FRONTEND FILES DETECTED: - Total Frontend Files: {len(frontend_files)} @@ -7312,14 +7288,14 @@ FRONTEND FILES DETECTED: SAMPLE FRONTEND FILES: {frontend_files_text[:5000]} -Provide a COMPREHENSIVE explanation covering: -1. What frontend files are present and what each type does (HTML, CSS, JavaScript) -2. How the frontend works step-by-step (like explaining to someone who has never seen code) -3. How users interact with the frontend -4. How data flows through the system -5. The structure and organization of the frontend files +Deliver a structured explanation covering: +1. File types present and the responsibilities they implement. +2. Step-by-step technical description of rendering, event handling, and data flow. +3. Component/module organization and how information moves between them. +4. Routing or navigation behavior, if applicable. +5. Key risks, limitations, or improvement areas supported by code evidence. -Write at least 1500 words. Use simple analogies and real-world examples. Be extremely detailed. +Write at least 1000 words and adhere strictly to the technical style. """ retry_message = self.client.messages.create( @@ -7342,24 +7318,28 @@ Write at least 1500 words. Use simple analogies and real-world examples. Be extr basic_analysis = f""" **FRONTEND ARCHITECTURE ANALYSIS** -**Overview:** -This repository contains {len(frontend_files)} frontend files with a total of {total_frontend_lines:,} lines of code. +**Overview** +This repository contains {len(frontend_files)} frontend files with a total of {total_frontend_lines:,} lines of code. The following summary is generated without AI assistance. -**Frontend File Types Detected:** -- HTML Files: {len(html_files)} files - These are the structure/skeleton of web pages (like the framework of a house) -- CSS Files: {len(css_files)} files - These define the styling and appearance (like paint and decoration) -- JavaScript Files: {len(js_files)} files - These add interactivity and functionality (like electrical systems and appliances) -- TypeScript Files: {len(ts_files)} files - These are enhanced JavaScript files with type checking +**Frontend File Types Detected** +- HTML Files: {len(html_files)} files - define the DOM structure and static content for each view. +- CSS Files: {len(css_files)} files - implement styling rules, layout definitions, and responsive behavior. +- JavaScript Files: {len(js_files)} files - provide interaction handlers, client-side logic, and integration with backend services. +- TypeScript Files: {len(ts_files)} files - supply strongly typed client logic compiled to JavaScript. -**How the Frontend Works (Simple Explanation):** -1. HTML files create the structure - they define what elements appear on the page (like headings, buttons, forms) -2. CSS files style these elements - they control colors, sizes, layouts, and visual appearance -3. JavaScript/TypeScript files add behavior - they make things happen when users interact (clicking buttons, submitting forms, loading data) +**Execution Flow** +1. HTML documents load in the browser and reference the required CSS and JavaScript bundles. +2. CSS stylesheets apply layout, typography, and responsive rules to the rendered DOM. +3. JavaScript or TypeScript modules register event listeners, hydrate components, and coordinate data retrieval. +4. State updates trigger DOM mutations through the frontend framework, refreshing the visible UI. -**Frontend Structure:** -The frontend is organized into different files that work together to create a complete web application. +**Structure Summary** +- Frontend source files are grouped by feature or component to deliver the user interface. +- Styling assets are maintained separately to control presentation concerns. +- Interactive behavior is encapsulated within JavaScript or TypeScript modules. -**Note:** Detailed AI analysis was not available, but the frontend files have been detected and analyzed. +**Note** +Detailed AI analysis was unavailable; this summary is derived from static inspection of the repository contents. """ ai_analysis = basic_analysis @@ -7467,95 +7447,40 @@ The frontend is organized into different files that work together to create a co js_files = [fa for fa in frontend_files_detected if fa.path.lower().endswith(('.js', '.jsx', '.mjs', '.cjs'))] ts_files = [fa for fa in frontend_files_detected if fa.path.lower().endswith(('.ts', '.tsx'))] - # Generate comprehensive basic analysis for non-technical audience + # Generate comprehensive basic analysis (technical fallback) basic_analysis = f""" -**1. FRONTEND OVERVIEW - WHAT IS THE FRONTEND?** +**1. FRONTEND OVERVIEW** +Detected {len(frontend_files_detected)} frontend-related files totaling {total_frontend_lines:,} lines of client-side code. -The frontend is the part of the application that users see and interact with in their web browser. Think of it like the visible part of an iceberg - what users see on their screen. +**2. FILE TYPE SUMMARY** +- HTML Files ({len(html_files)}): define document structure, semantic layout, and script mount points. +- CSS Files ({len(css_files)}): manage layout, typography, spacing, and responsive behaviour. +- JavaScript Files ({len(js_files)}): provide interaction handlers, API connectivity, and state management. +- TypeScript Files ({len(ts_files)}): supply typed sources that compile to JavaScript for runtime execution. -This repository contains {len(frontend_files_detected)} frontend files with a total of {total_frontend_lines:,} lines of code that create the user interface. +**3. RUNTIME EXECUTION FLOW** +1. Browsers load HTML documents and create the DOM tree. +2. CSS stylesheets apply presentation rules across breakpoints. +3. JavaScript or TypeScript modules initialise, attach event listeners, and hydrate UI components. +4. State changes trigger DOM updates via framework abstractions or direct DOM APIs. -**2. FRONTEND FILE TYPES - WHAT EACH TYPE DOES** +**4. USER INTERACTION PIPELINE** +- Button callbacks execute JavaScript routines to drive navigation, trigger side effects, or fetch data. +- Form workflows validate input locally, issue HTTP requests, and update the interface with success or error states. -**HTML Files ({len(html_files)} files):** -- HTML files are like the skeleton or framework of a building -- They define WHAT appears on the page (headings, buttons, forms, text, images) -- Think of HTML as the structure - like the walls and rooms of a house -- These files create the basic layout and content structure +**5. DATA FLOW** +1. User actions invoke JavaScript functions that compose network requests. +2. Backend endpoints respond with structured payloads (typically JSON or HTML). +3. The frontend updates local state and rerenders affected DOM nodes. +4. CSS keeps presentation consistent after dynamic updates. -**CSS Files ({len(css_files)} files):** -- CSS files are like the paint, decoration, and interior design -- They control HOW things look (colors, sizes, spacing, fonts, layouts) -- Think of CSS as the styling - making the house look beautiful -- These files make the page visually appealing and organized +**6. STRUCTURE AND ORGANISATION** +- Markup, styling, and behavioural concerns are separated for maintainability. +- Script modules coordinate data loading, state transitions, and rendering logic. +- Styling assets encapsulate layout concerns independently of business logic. -**JavaScript Files ({len(js_files)} files):** -- JavaScript files are like the electrical system and appliances -- They add INTERACTIVITY and FUNCTIONALITY (clicking buttons, submitting forms, loading data) -- Think of JavaScript as the "smarts" - making things work when you click them -- These files make the page dynamic and responsive to user actions - -**TypeScript Files ({len(ts_files)} files):** -- TypeScript files are enhanced JavaScript files with better error checking -- They work the same as JavaScript but with additional safety features -- Think of TypeScript as JavaScript with better quality control - -**3. HOW THE FRONTEND WORKS - STEP-BY-STEP EXPLANATION** - -**Step 1: Loading the Page** -When a user opens the website, the browser reads the HTML file first. This tells the browser what elements to display (like a blueprint tells builders what to build). - -**Step 2: Styling the Page** -Next, the browser reads the CSS files. These tell the browser how to style each element - what colors to use, how big things should be, where to place them (like interior designers telling builders how to decorate). - -**Step 3: Making It Interactive** -Finally, the browser runs the JavaScript/TypeScript files. These add the "brain" - making buttons clickable, forms submittable, and data loadable (like installing electrical systems and appliances). - -**4. USER INTERACTION FLOW** - -**When a User Clicks a Button:** -1. The HTML defines where the button is -2. The CSS makes it look like a button (colored, styled) -3. The JavaScript detects the click -4. The JavaScript performs the action (like sending data to the server) -5. The page updates to show the result - -**When a User Fills a Form:** -1. The HTML creates the form structure (input fields, labels) -2. The CSS styles the form (makes it look nice) -3. The JavaScript validates the input (checks if it's correct) -4. The JavaScript sends the data to the server -5. The page shows a success or error message - -**5. DATA FLOW - HOW INFORMATION MOVES** - -**Getting Data from Server:** -1. User clicks a button or loads a page -2. JavaScript sends a request to the server (like ordering food) -3. Server processes the request and sends back data (like the kitchen preparing food) -4. JavaScript receives the data (like receiving the food) -5. JavaScript updates the HTML to show the data (like displaying it on the plate) -6. CSS styles the data display (like arranging the food nicely) - -**6. STRUCTURE AND ORGANIZATION** - -The frontend files are organized in a way that makes them easy to maintain: -- HTML files define the structure -- CSS files control the appearance -- JavaScript files add the functionality - -They all work together like parts of a machine - each part has a specific job, but they all need to work together for the machine to function properly. - -**7. FRONTEND ARCHITECTURE SUMMARY** - -This frontend uses a traditional web architecture: -- HTML provides the foundation (structure) -- CSS provides the styling (appearance) -- JavaScript provides the behavior (functionality) - -Together, these files create a complete, interactive web application that users can see, use, and interact with in their web browsers. - -**Note:** Detailed AI analysis encountered an error, but all frontend files have been successfully detected and analyzed. The frontend is fully functional and ready for use. +**7. SUMMARY** +This fallback summary is produced from static inspection because AI synthesis was unavailable during the run. Re-run the automated analysis when the AI service is reachable for richer insight. """ return { diff --git a/services/ai-analysis-service/env.example b/services/ai-analysis-service/env.example index 2b8c19d..d6095cf 100644 --- a/services/ai-analysis-service/env.example +++ b/services/ai-analysis-service/env.example @@ -6,7 +6,17 @@ HOST=0.0.0.0 NODE_ENV=development # AI API Keys -ANTHROPIC_API_KEY=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 +ANTHROPIC_API_KEY=your_anthropic_api_key + +# Neo4j Knowledge Graph Configuration +USE_NEO4J_KG=true +NEO4J_URI=bolt://localhost:7687 +NEO4J_USER=neo4j +NEO4J_PASSWORD=secure_neo4j_2024 +NEO4J_DATABASE=neo4j + +# Report Configuration +REPORT_TECHNICAL_ONLY=false # Database Configuration POSTGRES_HOST=localhost diff --git a/services/ai-analysis-service/knowledge_graph/__init__.py b/services/ai-analysis-service/knowledge_graph/__init__.py new file mode 100644 index 0000000..b4bc108 --- /dev/null +++ b/services/ai-analysis-service/knowledge_graph/__init__.py @@ -0,0 +1,9 @@ +""" +Knowledge graph utilities for the AI Analysis Service. + +This package provides the Neo4j client and high-level helpers used to +persist and query analysis results in a graph representation. +""" + +from .neo4j_client import Neo4jGraphClient # noqa: F401 + diff --git a/services/ai-analysis-service/knowledge_graph/neo4j_client.py b/services/ai-analysis-service/knowledge_graph/neo4j_client.py new file mode 100644 index 0000000..d3790ae --- /dev/null +++ b/services/ai-analysis-service/knowledge_graph/neo4j_client.py @@ -0,0 +1,328 @@ +""" +Neo4j client helpers for the AI Analysis Service. + +This module wraps the official Neo4j async driver and exposes a minimal +set of convenience methods that we can reuse across the service without +sprinkling Cypher execution boilerplate everywhere. +""" + +from __future__ import annotations + +import json +from contextlib import asynccontextmanager +from dataclasses import dataclass +from datetime import datetime +from typing import Any, AsyncIterator, Dict, List, Optional, Sequence + +from neo4j import AsyncGraphDatabase + +try: + from neo4j import AsyncResult, AsyncSession # type: ignore +except ImportError: # pragma: no cover - fallback for older/newer driver versions + AsyncResult = Any # type: ignore + AsyncSession = Any # type: ignore + + +def _json_dumps(value: Any) -> str: + """Serialize complex values so we can persist them as strings safely.""" + if value is None: + return "" + if isinstance(value, (str, int, float, bool)): + return str(value) + try: + return json.dumps(value, default=str) + except Exception: + return str(value) + + +@dataclass +class Neo4jConfig: + uri: str + user: str + password: str + database: Optional[str] = None + fetch_size: int = 1000 + + +class Neo4jGraphClient: + """ + Thin wrapper around the Neo4j async driver that provides helpers for + writing analysis artefacts into the graph and querying them back. + """ + + def __init__(self, config: Neo4jConfig) -> None: + self._config = config + self._driver = AsyncGraphDatabase.driver( + config.uri, + auth=(config.user, config.password), + # Allow long running operations while the analysis progresses + max_connection_lifetime=3600, + ) + + async def close(self) -> None: + if self._driver: + await self._driver.close() + + @asynccontextmanager + async def session(self) -> AsyncIterator[AsyncSession]: + kwargs: Dict[str, Any] = {} + if self._config.database: + kwargs["database"] = self._config.database + if self._config.fetch_size: + kwargs["fetch_size"] = self._config.fetch_size + async with self._driver.session(**kwargs) as session: + yield session + + async def _run_write(self, query: str, **params: Any) -> None: + async with self.session() as session: + async def _write(tx): + result = await tx.run(query, **params) + await result.consume() + await session.execute_write(_write) + + async def _run_read(self, query: str, **params: Any) -> List[Dict[str, Any]]: + async with self.session() as session: + result: AsyncResult = await session.run(query, **params) + records = await result.data() + return records + + # ------------------------------------------------------------------ # + # Write helpers + # ------------------------------------------------------------------ # + + async def upsert_run(self, run_id: str, repository_id: str) -> None: + await self._run_write( + """ + MERGE (r:Run {run_id: $run_id}) + ON CREATE SET + r.repository_id = $repository_id, + r.created_at = datetime(), + r.updated_at = datetime() + ON MATCH SET + r.repository_id = $repository_id, + r.updated_at = datetime() + """, + run_id=run_id, + repository_id=repository_id, + ) + + async def clear_run(self, run_id: str) -> None: + await self._run_write( + """ + MATCH (r:Run {run_id: $run_id}) + OPTIONAL MATCH (r)-[rel]-() + DETACH DELETE r + """, + run_id=run_id, + ) + + async def upsert_module_graph( + self, + run_id: str, + repository_id: str, + module_props: Dict[str, Any], + files: Sequence[Dict[str, Any]], + findings: Sequence[Dict[str, Any]], + dependencies: Sequence[Dict[str, Any]], + ) -> None: + """ + Persist module level artefacts in a single transaction. + """ + # Ensure strings + module_props = {k: _json_dumps(v) if isinstance(v, (dict, list, tuple, set)) else v for k, v in module_props.items()} + files_payload = [ + { + "path": item["path"], + "props": { + key: _json_dumps(value) if isinstance(value, (dict, list, tuple, set)) else value + for key, value in item.get("props", {}).items() + }, + } + for item in files + ] + findings_payload = [ + { + "id": item["id"], + "props": { + key: _json_dumps(value) if isinstance(value, (dict, list, tuple, set)) else value + for key, value in item.get("props", {}).items() + }, + "file_path": item.get("file_path"), + } + for item in findings + ] + dependencies_payload = [ + { + "target": dependency.get("target"), + "kind": dependency.get("kind", "depends_on"), + "metadata": _json_dumps(dependency.get("metadata", {})), + } + for dependency in dependencies + ] + + await self._run_write( + """ + MERGE (r:Run {run_id: $run_id}) + ON CREATE SET + r.repository_id = $repository_id, + r.created_at = datetime(), + r.updated_at = datetime() + ON MATCH SET + r.repository_id = $repository_id, + r.updated_at = datetime() + + MERGE (m:Module {run_id: $run_id, name: $module_name}) + SET m += $module_props, + m.updated_at = datetime() + + MERGE (r)-[:RUN_HAS_MODULE]->(m) + + WITH m + UNWIND $files AS file_data + MERGE (f:File {run_id: $run_id, path: file_data.path}) + SET f += file_data.props, + f.updated_at = datetime() + MERGE (m)-[:MODULE_INCLUDES_FILE]->(f) + + WITH m + UNWIND $findings AS finding_data + MERGE (fd:Finding {run_id: $run_id, finding_id: finding_data.id}) + SET fd += finding_data.props, + fd.updated_at = datetime() + MERGE (m)-[:MODULE_HAS_FINDING]->(fd) + FOREACH (fp IN CASE WHEN finding_data.file_path IS NULL THEN [] ELSE [finding_data.file_path] END | + MERGE (ff:File {run_id: $run_id, path: fp}) + MERGE (fd)-[:FINDING_TOUCHES_FILE]->(ff) + ) + + WITH m + UNWIND $dependencies AS dependency + FOREACH (_ IN CASE WHEN dependency.target IS NULL THEN [] ELSE [1] END | + MERGE (dep:Module {run_id: $run_id, name: dependency.target}) + MERGE (m)-[rel:MODULE_DEPENDENCY {kind: dependency.kind}]->(dep) + SET rel.metadata = dependency.metadata, + rel.updated_at = datetime() + ) + """, + run_id=run_id, + repository_id=repository_id, + module_name=module_props.get("name"), + module_props=module_props, + files=files_payload, + findings=findings_payload, + dependencies=dependencies_payload, + ) + + async def upsert_run_state(self, run_id: str, state: Dict[str, Any]) -> None: + await self._run_write( + """ + MERGE (r:Run {run_id: $run_id}) + SET r.analysis_state = $state, + r.state_updated_at = datetime() + """, + run_id=run_id, + state=_json_dumps(state), + ) + + async def upsert_synthesis(self, run_id: str, synthesis: Dict[str, Any]) -> None: + await self._run_write( + """ + MERGE (r:Run {run_id: $run_id}) + SET r.synthesis_analysis = $synthesis, + r.synthesis_updated_at = datetime() + """, + run_id=run_id, + synthesis=_json_dumps(synthesis), + ) + + # ------------------------------------------------------------------ # + # Read helpers + # ------------------------------------------------------------------ # + + async def fetch_modules(self, run_id: str) -> List[Dict[str, Any]]: + records = await self._run_read( + """ + MATCH (r:Run {run_id: $run_id})-[:RUN_HAS_MODULE]->(m:Module) + OPTIONAL MATCH (m)-[:MODULE_INCLUDES_FILE]->(f:File) + OPTIONAL MATCH (m)-[:MODULE_HAS_FINDING]->(fd:Finding) + OPTIONAL MATCH (fd)-[:FINDING_TOUCHES_FILE]->(ff:File) + RETURN + m, + collect(DISTINCT properties(f)) AS files, + collect(DISTINCT properties(fd)) AS findings, + collect(DISTINCT properties(ff)) AS finding_files + """, + run_id=run_id, + ) + modules: List[Dict[str, Any]] = [] + for record in records: + module_node = record.get("m", {}) + files = record.get("files", []) + findings = record.get("findings", []) + finding_files = record.get("finding_files", []) + modules.append( + { + "module": module_node, + "files": files, + "findings": findings, + "finding_files": finding_files, + } + ) + return modules + + async def fetch_run_state(self, run_id: str) -> Optional[Dict[str, Any]]: + records = await self._run_read( + """ + MATCH (r:Run {run_id: $run_id}) + RETURN r.analysis_state AS analysis_state + """, + run_id=run_id, + ) + if not records: + return None + raw_state = records[0].get("analysis_state") + if not raw_state: + return None + try: + return json.loads(raw_state) + except json.JSONDecodeError: + return {"raw": raw_state} + + async def fetch_synthesis(self, run_id: str) -> Optional[Dict[str, Any]]: + records = await self._run_read( + """ + MATCH (r:Run {run_id: $run_id}) + RETURN r.synthesis_analysis AS synthesis + """, + run_id=run_id, + ) + if not records: + return None + raw_synthesis = records[0].get("synthesis") + if not raw_synthesis: + return None + try: + return json.loads(raw_synthesis) + except json.JSONDecodeError: + return {"raw": raw_synthesis} + + async def fetch_run_metadata(self, run_id: str) -> Optional[Dict[str, Any]]: + records = await self._run_read( + """ + MATCH (r:Run {run_id: $run_id}) + RETURN r + """, + run_id=run_id, + ) + if not records: + return None + run_node = records[0].get("r") + if not run_node: + return None + metadata = dict(run_node) + if "created_at" in metadata and isinstance(metadata["created_at"], datetime): + metadata["created_at"] = metadata["created_at"].isoformat() + if "updated_at" in metadata and isinstance(metadata["updated_at"], datetime): + metadata["updated_at"] = metadata["updated_at"].isoformat() + return metadata + diff --git a/services/ai-analysis-service/knowledge_graph/operations.py b/services/ai-analysis-service/knowledge_graph/operations.py new file mode 100644 index 0000000..8a37311 --- /dev/null +++ b/services/ai-analysis-service/knowledge_graph/operations.py @@ -0,0 +1,214 @@ +""" +High-level knowledge graph operations used by the AI Analysis Service. + +These helpers translate existing analysis objects into the node/relationship +structure expected by `Neo4jGraphClient`. +""" + +from __future__ import annotations + +import json +import uuid +from datetime import datetime +from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple + +from .neo4j_client import Neo4jGraphClient + + +def _safe_json(value: Any) -> str: + if value is None: + return "" + if isinstance(value, (str, int, float, bool)): + return str(value) + try: + return json.dumps(value, default=str) + except Exception: + return str(value) + + +def _normalize_issue(issue: Any, index: int) -> Tuple[str, Dict[str, Any]]: + """ + Convert an issue structure that might be a string or dict into a dict. + Returns (summary, props). + """ + if isinstance(issue, dict): + summary = issue.get("title") or issue.get("issue") or issue.get("description") or f"Issue #{index}" + props = { + "summary": summary, + "severity": issue.get("severity", "medium"), + "category": issue.get("category", "general"), + "description": issue.get("description") or issue.get("details") or "", + "recommendation": issue.get("recommendation") or issue.get("action") or "", + "evidence": _safe_json(issue.get("evidence")), + } + if issue.get("impact"): + props["impact"] = issue["impact"] + if issue.get("line_number"): + props["line_number"] = issue["line_number"] + return summary, props + summary = str(issue) + return summary, { + "summary": summary, + "severity": "medium", + "category": "general", + } + + +def build_module_payload( + run_id: str, + repository_id: str, + module_name: str, + chunk: Dict[str, Any], + chunk_analysis: Dict[str, Any], + file_analyses: Sequence[Any], + metadata: Dict[str, Any], + ai_response: str, +) -> Dict[str, Any]: + """Prepare module level payload for graph insertion.""" + module_id = chunk.get("id") or str(uuid.uuid4()) + module_quality = chunk_analysis.get("module_quality_score") + module_overview = chunk_analysis.get("module_overview", "") + module_architecture = chunk_analysis.get("module_architecture", "") + module_security = chunk_analysis.get("module_security_assessment", "") + module_recommendations = chunk_analysis.get("module_recommendations", []) + + files: List[Dict[str, Any]] = [] + findings: List[Dict[str, Any]] = [] + + total_issues = 0 + total_recommendations = 0 + + for fa_index, fa in enumerate(file_analyses): + path = getattr(fa, "path", None) or getattr(fa, "file_path", "unknown") + issues = getattr(fa, "issues_found", None) or [] + recommendations = getattr(fa, "recommendations", None) or [] + total_issues += len(issues) if isinstance(issues, (list, tuple)) else 0 + total_recommendations += len(recommendations) if isinstance(recommendations, (list, tuple)) else 0 + + files.append( + { + "path": str(path), + "props": { + "language": getattr(fa, "language", "unknown"), + "lines_of_code": getattr(fa, "lines_of_code", 0), + "complexity_score": getattr(fa, "complexity_score", 0), + "severity_score": getattr(fa, "severity_score", 0), + }, + } + ) + + if isinstance(issues, Iterable): + for issue_index, raw_issue in enumerate(issues): + summary, issue_props = _normalize_issue(raw_issue, issue_index) + finding_id = f"{module_id}:{fa_index}:{issue_index}" + issue_props.update( + { + "module": module_name, + "file_path": str(path), + "created_at": datetime.utcnow().isoformat(), + } + ) + findings.append( + { + "id": finding_id, + "props": issue_props, + "file_path": str(path), + } + ) + + module_props: Dict[str, Any] = { + "name": module_name, + "module_id": module_id, + "quality_score": module_quality, + "overview": module_overview, + "architecture": module_architecture, + "security": module_security, + "recommendations": module_recommendations, + "analysis_payload": metadata, + "ai_response": ai_response, + "repository_id": repository_id, + "total_files": len(file_analyses), + "total_issues": total_issues, + "total_recommendations": total_recommendations, + "updated_at": datetime.utcnow().isoformat(), + } + + dependencies = [] + for dependency in metadata.get("dependencies", {}).get("depends_on_chunks", []): + dependencies.append( + { + "target": dependency, + "kind": "depends_on", + "metadata": {"source": module_name}, + } + ) + + return { + "module_props": module_props, + "files": files, + "findings": findings, + "dependencies": dependencies, + } + + +async def store_module_analysis( + client: Neo4jGraphClient, + run_id: str, + repository_id: str, + module_payload: Dict[str, Any], +) -> None: + await client.upsert_module_graph( + run_id=run_id, + repository_id=repository_id, + module_props=module_payload["module_props"], + files=module_payload["files"], + findings=module_payload["findings"], + dependencies=module_payload["dependencies"], + ) + + +async def store_analysis_state(client: Neo4jGraphClient, run_id: str, analysis_state: Dict[str, Any]) -> None: + await client.upsert_run_state(run_id=run_id, state=analysis_state) + + +async def store_synthesis(client: Neo4jGraphClient, run_id: str, synthesis: Dict[str, Any]) -> None: + await client.upsert_synthesis(run_id=run_id, synthesis=synthesis) + + +async def fetch_module_analyses(client: Neo4jGraphClient, run_id: str) -> List[Dict[str, Any]]: + modules = await client.fetch_modules(run_id) + module_analyses: List[Dict[str, Any]] = [] + for entry in modules: + node = entry.get("module", {}) + files = entry.get("files", []) + findings = entry.get("findings", []) + analysis_payload = node.get("analysis_payload") + if isinstance(analysis_payload, str): + try: + analysis_payload = json.loads(analysis_payload) + except json.JSONDecodeError: + analysis_payload = {"raw": analysis_payload} + module_analyses.append( + { + "module_name": node.get("name"), + "module_id": node.get("module_id"), + "quality_score": node.get("quality_score"), + "module_overview": node.get("overview"), + "module_architecture": node.get("architecture"), + "module_security_assessment": node.get("security"), + "module_recommendations": node.get("recommendations"), + "files_analyzed": [file.get("path") for file in files if file.get("path")], + "raw_payload": analysis_payload, + "findings": findings, + } + ) + return module_analyses + + +async def fetch_run_state(client: Neo4jGraphClient, run_id: str) -> Optional[Dict[str, Any]]: + return await client.fetch_run_state(run_id) + + +async def fetch_synthesis(client: Neo4jGraphClient, run_id: str) -> Optional[Dict[str, Any]]: + return await client.fetch_synthesis(run_id) + diff --git a/services/ai-analysis-service/progress_manager.py b/services/ai-analysis-service/progress_manager.py index 8be4687..2a7ff21 100644 --- a/services/ai-analysis-service/progress_manager.py +++ b/services/ai-analysis-service/progress_manager.py @@ -19,6 +19,7 @@ class AnalysisProgressManager: self.subscribers: List[asyncio.Queue] = [] self.redis_client: Optional[redis.Redis] = None self.progress_key = f"analysis_progress:{analysis_id}" + self._complete: bool = False async def connect_redis(self): """Connect to Redis for progress persistence""" @@ -103,6 +104,9 @@ class AnalysisProgressManager: self.unsubscribe(queue) print(f"šŸ“¤ Event emitted: {event_type} - {data.get('message', '')}") + + if event_type in {"analysis_completed", "analysis_error"}: + self._complete = True async def get_progress_history(self) -> List[Dict[str, Any]]: """Retrieve progress history from Redis""" @@ -125,6 +129,12 @@ class AnalysisProgressManager: except Exception as e: print(f"āš ļø Failed to clear progress: {e}") + self._complete = False + + def is_complete(self) -> bool: + """Return whether the analysis has completed or errored.""" + return self._complete + class GlobalProgressTracker: """Global singleton to track all active analyses""" diff --git a/services/ai-analysis-service/pyproject.toml b/services/ai-analysis-service/pyproject.toml new file mode 100644 index 0000000..69a2cc4 --- /dev/null +++ b/services/ai-analysis-service/pyproject.toml @@ -0,0 +1,32 @@ +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "ai-analysis-service" +version = "0.1.0" +description = "AI Analysis Microservice for Code Repository Analysis" +requires-python = ">=3.9" + +dependencies = [ + "fastapi>=0.104.1", + "uvicorn>=0.24.0", + "pydantic>=2.5.0", + "httpx>=0.25.0", + "redis>=4.5.0", + "psycopg2-binary>=2.9.7", + "neo4j>=5.8.0", + "anthropic>=0.7.0", + "python-dotenv>=1.0.0" +] + +[project.optional-dependencies] +dev = [ + "pytest", + "mypy", + "black", + "isort" +] + +[tool.setuptools] +packages = ["ai_analysis_service"] diff --git a/services/ai-analysis-service/requirements.txt b/services/ai-analysis-service/requirements.txt index f0fc6b8..0d8ce10 100644 --- a/services/ai-analysis-service/requirements.txt +++ b/services/ai-analysis-service/requirements.txt @@ -17,6 +17,7 @@ GitPython>=3.1.40 redis>=4.5.0 pymongo>=4.5.0 psycopg2-binary>=2.9.7 +neo4j>=5.8.0 # Neo4j Graph Database Driver # Data processing numpy>=1.24.0 diff --git a/services/ai-analysis-service/server.py b/services/ai-analysis-service/server.py index 99bf99c..77d9bf0 100644 --- a/services/ai-analysis-service/server.py +++ b/services/ai-analysis-service/server.py @@ -26,13 +26,12 @@ import uvicorn import mimetypes import httpx import redis +import psycopg2 +from psycopg2.extras import RealDictCursor -# PostgreSQL cursor for querying -try: - from psycopg2.extras import RealDictCursor -except ImportError: - # Fallback if psycopg2 not available - RealDictCursor = None +from knowledge_graph import Neo4jGraphClient +from knowledge_graph.neo4j_client import Neo4jConfig +from knowledge_graph import operations as kg_ops # Import the AI analysis components # Note: ai-analyze.py has a hyphen, so we need to handle the import specially @@ -40,7 +39,7 @@ import sys import importlib.util # Load the ai-analyze.py module -spec = importlib.util.spec_from_file_location("ai_analyze", "ai-analyze.py") +spec = importlib.util.spec_from_file_location("ai_analyze", "./ai-analyze.py") ai_analyze_module = importlib.util.module_from_spec(spec) sys.modules["ai_analyze"] = ai_analyze_module spec.loader.exec_module(ai_analyze_module) @@ -52,7 +51,6 @@ from ai_analyze import ( ArchitectureAnalysis, SecurityAnalysis, CodeQualityAnalysis, - PerformanceAnalysis, Issue, ModuleAnalysis, ModuleSummary @@ -71,12 +69,14 @@ from progress_manager import AnalysisProgressManager, progress_tracker # Global analyzer instance analyzer = None +neo4j_client: Optional[Neo4jGraphClient] = None +USE_KNOWLEDGE_GRAPH = False @asynccontextmanager async def lifespan(app: FastAPI): """Lifespan context manager for startup and shutdown events.""" # Startup - global analyzer + global analyzer, neo4j_client, USE_KNOWLEDGE_GRAPH try: # Load environment variables from dotenv import load_dotenv @@ -116,6 +116,26 @@ async def lifespan(app: FastAPI): analyzer = EnhancedGitHubAnalyzer(api_key, config) print("āœ… AI Analysis Service initialized successfully") + + use_kg_flag = os.getenv("USE_NEO4J_KG", os.getenv("USE_KNOWLEDGE_GRAPH", "false")) + USE_KNOWLEDGE_GRAPH = str(use_kg_flag).lower() in ("1", "true", "yes", "on") + if USE_KNOWLEDGE_GRAPH: + try: + neo4j_config = Neo4jConfig( + uri=os.getenv("NEO4J_URI", "bolt://localhost:7687"), + user=os.getenv("NEO4J_USER", "neo4j"), + password=os.getenv("NEO4J_PASSWORD", "neo4j"), + database=os.getenv("NEO4J_DATABASE") or None, + ) + neo4j_client = Neo4jGraphClient(neo4j_config) + print(f"āœ… Knowledge graph enabled (Neo4j URI: {neo4j_config.uri})") + except Exception as kg_error: + neo4j_client = None + USE_KNOWLEDGE_GRAPH = False + print(f"āš ļø Failed to initialize Neo4j client: {kg_error}. Falling back to episodic memory.") + else: + neo4j_client = None + print("ā„¹ļø Knowledge graph disabled (falling back to episodic memory)") except Exception as e: print(f"āŒ Failed to initialize AI Analysis Service: {e}") raise @@ -124,6 +144,8 @@ async def lifespan(app: FastAPI): # Shutdown (if needed) # Cleanup code can go here if needed + if neo4j_client: + await neo4j_client.close() app = FastAPI( title="AI Analysis Service", @@ -624,6 +646,13 @@ git_client = GitIntegrationClient() analysis_cache = AnalysisCache() content_optimizer = ContentOptimizer() +def get_progress_manager(analysis_id: str) -> AnalysisProgressManager: + """Retrieve an existing progress manager or create one if missing.""" + manager = progress_tracker.get_manager(analysis_id) + if manager is None: + manager = progress_tracker.create_manager(analysis_id) + return manager + # ============================================================================ # TOKEN USAGE & COST TRACKING (NEW) # ============================================================================ @@ -1075,56 +1104,37 @@ async def stream_progress(analysis_id: str, request: Request): }; """ async def event_generator(): - # Get or create progress manager - manager = progress_tracker.get_manager(analysis_id) - - if not manager: - # Send error and close - yield f"data: {json.dumps({'error': 'Analysis not found'})}\n\n" - return - - # Subscribe to updates - queue = manager.subscribe() - + # Properly handle event generation + progress_mgr: Optional[AnalysisProgressManager] = None + subscriber_queue: Optional[asyncio.Queue] = None try: - # Send historical events first - history = await manager.get_progress_history() - for event in history: - if await request.is_disconnected(): - break - yield f"data: {json.dumps(event)}\n\n" + progress_mgr = get_progress_manager(analysis_id) - # Stream new events + # Make sure Redis connection exists so we can replay history + if progress_mgr.redis_client is None: + await progress_mgr.connect_redis() + + # Replay cached history first + history = await progress_mgr.get_progress_history() + for event in history: + yield f"data: {json.dumps(event)}\n\n" + + # Subscribe to new events + subscriber_queue = progress_mgr.subscribe() + while True: - if await request.is_disconnected(): + event = await subscriber_queue.get() + yield f"data: {json.dumps(event)}\n\n" + if event.get("event") in ("analysis_completed", "analysis_error"): break - - try: - # Wait for next event with timeout - event = await asyncio.wait_for(queue.get(), timeout=30.0) - yield f"data: {json.dumps(event)}\n\n" - - # If analysis completed, close stream - if event.get('event') in ['analysis_completed', 'analysis_error']: - break - - except asyncio.TimeoutError: - # Send keepalive ping - yield f": keepalive\n\n" - continue - + except Exception as e: + error_payload = {"error": str(e)} + yield f"data: {json.dumps(error_payload)}\n\n" finally: - manager.unsubscribe(queue) + if progress_mgr and subscriber_queue: + progress_mgr.unsubscribe(subscriber_queue) - return StreamingResponse( - event_generator(), - media_type="text/event-stream", - headers={ - "Cache-Control": "no-cache", - "Connection": "keep-alive", - "X-Accel-Buffering": "no" # Disable nginx buffering - } - ) + return StreamingResponse(event_generator(), media_type="text/event-stream") @app.post("/analyze") async def analyze_repository(request: AnalysisRequest, background_tasks: BackgroundTasks): @@ -3190,187 +3200,32 @@ async def analyze_files_smart_batch(files_batch: List[Tuple[str, str]], reposito async def store_chunk_analysis_in_memory(chunk: Dict, file_analyses: List, chunk_analysis: Dict, repository_id: str, session_id: str = None, analysis_state: Optional[Dict] = None): """ - Store detailed chunk-level analysis in episodic memory (MongoDB). - Creates one record per chunk with comprehensive analysis data. - Now includes progressive context (Option 3: Hybrid Approach). + Store chunk analysis in memory using either Neo4j Knowledge Graph or Episodic Memory. + Supports fallback mechanisms for robust storage. """ try: - if not analyzer or not hasattr(analyzer, 'memory_manager'): - print("āš ļø [MEMORY] Memory manager not available, skipping chunk storage") - return - - # Get session ID from analyzer - if not session_id: - session_id = getattr(analyzer, 'session_id', str(uuid.uuid4())) - - chunk_id = chunk.get('id', 'unknown') - chunk_name = chunk.get('name', 'unknown') - chunk_type = chunk.get('chunk_type', 'module') - chunk_priority = chunk.get('priority', 2) - dependencies = chunk.get('context_dependencies', []) - - # Calculate chunk metrics + # Validate input parameters + if not chunk or not file_analyses: + print("āŒ [MEMORY] Invalid chunk or file_analyses") + return None + + # Extract necessary variables (these were missing in the original implementation) + chunk_name = chunk.get('name', 'Unknown Chunk') + chunk_type = chunk.get('type', 'generic') + chunk_priority = chunk.get('priority', 5) total_files = len(file_analyses) - total_lines = sum(fa.lines_of_code for fa in file_analyses if fa.lines_of_code is not None) - total_issues = sum(len(fa.issues_found) if isinstance(fa.issues_found, (list, tuple)) else 0 for fa in file_analyses) - total_recommendations = sum(len(fa.recommendations) if isinstance(fa.recommendations, (list, tuple)) else 0 for fa in file_analyses) - - # Calculate quality distribution - high_quality = len([fa for fa in file_analyses if fa.severity_score >= 8]) - medium_quality = len([fa for fa in file_analyses if 5 <= fa.severity_score < 8]) - low_quality = len([fa for fa in file_analyses if fa.severity_score < 5]) - - # Get module quality score from chunk_analysis or calculate from files - module_quality = chunk_analysis.get('module_quality_score', - sum(fa.severity_score for fa in file_analyses if fa.severity_score is not None) / total_files if total_files > 0 else 5.0) - - # Build comprehensive AI response text with CODE EVIDENCE - # FIX: Convert all values to strings immediately to prevent TypeError - module_overview = chunk_analysis.get('module_overview', f"Analysis of {chunk_name} module") - if isinstance(module_overview, dict): - module_overview = json.dumps(module_overview, indent=2) - else: - module_overview = str(module_overview) - - # Extract code evidence from file analyses for concrete proof in reports - try: - code_evidence = extract_code_evidence_from_files(file_analyses) - print(f" šŸ“ø Extracted {len(code_evidence)} evidence items") - except Exception as e: - print(f" āš ļø Code evidence extraction failed: {e}") - code_evidence = [] - - module_architecture = chunk_analysis.get('module_architecture', 'Architecture analysis in progress') - if isinstance(module_architecture, dict): - module_architecture = json.dumps(module_architecture, indent=2) - else: - module_architecture = str(module_architecture) - - module_security = chunk_analysis.get('module_security_assessment', 'Security assessment in progress') - if isinstance(module_security, dict): - module_security = json.dumps(module_security, indent=2) - else: - module_security = str(module_security) - - ai_response_parts = [ - f"# COMPREHENSIVE ANALYSIS: {chunk_name.upper()}", - f"Chunk ID: {chunk_id}", - f"Chunk Type: {chunk_type}", - "", - f"## MODULE OVERVIEW", - module_overview, - "", - f"## MODULE METRICS", - f"- Module Quality Score: {module_quality:.1f}/10", - f"- Total Files: {total_files}", - f"- Total Lines of Code: {total_lines:,}", - f"- Total Issues: {total_issues}", - f"- Total Recommendations: {total_recommendations}", - f"- High Quality Files (Score >= 8): {high_quality}", - f"- Medium Quality Files (Score 5-7): {medium_quality}", - f"- Low Quality Files (Score < 5): {low_quality}", - "", - f"## ARCHITECTURE ASSESSMENT", - module_architecture, - "", - f"## SECURITY ASSESSMENT", - module_security, - "", - f"## MODULE RECOMMENDATIONS", - ] - - module_recs = chunk_analysis.get('module_recommendations', []) - if module_recs: - for rec in module_recs: - # Handle both string and dict recommendations - if isinstance(rec, dict): - rec_text = rec.get('text', str(rec.get('recommendation', '')))[:200] - else: - rec_text = str(rec) - ai_response_parts.append(f"- {rec_text}") - else: - ai_response_parts.append("- Review module structure") - - ai_response_parts.extend([ - "", - "## CODE EVIDENCE & FINDINGS", - "" - ]) - - # Add code evidence section - if code_evidence: - ai_response_parts.append("### SPECIFIC CODE ISSUES WITH EVIDENCE:") - for evidence in code_evidence[:10]: # Top 10 most critical - ai_response_parts.extend([ - f"**File:** {evidence['file']}", - f"**Issue:** {evidence['issue']}", - f"**Line {evidence['line_number']}:**", - "```" + evidence['language'], - evidence['code_snippet'], - "```", - f"**Recommendation:** {evidence['recommendation']}", - "" - ]) - - ai_response_parts.extend([ - "", - "## FILE-LEVEL ANALYSIS SUMMARY", - "" - ]) - - # Add detailed file analyses - for fa in file_analyses: - ai_response_parts.extend([ - f"### {fa.path}", - f"- Language: {fa.language}", - f"- Lines of Code: {fa.lines_of_code}", - f"- Quality Score: {fa.severity_score:.1f}/10", - f"- Complexity Score: {fa.complexity_score:.1f}/10", - f"- Issues: {len(fa.issues_found) if isinstance(fa.issues_found, (list, tuple)) else 0}", - "" - ]) - - if fa.issues_found: - ai_response_parts.append("**Issues Found:**") - for issue in fa.issues_found[:5]: # Top 5 issues - # Handle both string and dict issues - if isinstance(issue, dict): - issue_text = issue.get('title', str(issue.get('description', '')))[:200] - else: - issue_text = str(issue) - ai_response_parts.append(f"- {issue_text}") - ai_response_parts.append("") - - if fa.recommendations: - ai_response_parts.append("**Recommendations:**") - for rec in fa.recommendations[:5]: # Top 5 recommendations - # Handle both string and dict recommendations - if isinstance(rec, dict): - rec_text = rec.get('text', str(rec.get('recommendation', '')))[:200] - else: - rec_text = str(rec) - ai_response_parts.append(f"- {rec_text}") - ai_response_parts.append("") - - if fa.detailed_analysis: - # Ensure detailed_analysis is a string, not a dict - detailed_analysis_text = str(fa.detailed_analysis) if not isinstance(fa.detailed_analysis, str) else fa.detailed_analysis - ai_response_parts.extend([ - "**Detailed Analysis:**", - detailed_analysis_text, - "" - ]) - - # Final safety check: Convert all items to strings before joining + total_lines = sum(fa.lines_of_code for fa in file_analyses) + dependencies = chunk.get('dependencies', []) + module_quality = chunk_analysis.get('module_quality', 5.0) + total_issues = sum(len(fa.issues_found) for fa in file_analyses) + total_recommendations = sum(len(fa.recommendations) for fa in file_analyses) + high_quality = len([fa for fa in file_analyses if fa.complexity_score and fa.complexity_score <= 3]) + medium_quality = len([fa for fa in file_analyses if fa.complexity_score and 3 < fa.complexity_score <= 7]) + low_quality = len([fa for fa in file_analyses if fa.complexity_score and fa.complexity_score > 7]) + + # Prepare AI response (this was also missing) ai_response_parts_clean = [] - for item in ai_response_parts: - if isinstance(item, dict): - # Convert dict to JSON string (json is already imported at module level) - ai_response_parts_clean.append(json.dumps(item, indent=2)) - elif isinstance(item, (list, tuple)): - # Convert list/tuple to string representation - ai_response_parts_clean.append(str(item)) - else: + for item in chunk_analysis.get('ai_response_parts', []): ai_response_parts_clean.append(str(item)) ai_response = "\n".join(ai_response_parts_clean) @@ -3379,105 +3234,69 @@ async def store_chunk_analysis_in_memory(chunk: Dict, file_analyses: List, chunk file_names = [fa.path for fa in file_analyses] user_query = f"Analysis of chunk: {chunk_name} ({chunk_type}) - {total_files} files: {', '.join(file_names[:5])}{'...' if len(file_names) > 5 else ''}" - # Prepare file analyses data for storage (OPTIMIZATION: Store only paths, not content) - # IMPORTANT: Never store file content in episodic memory to save storage space + # Prepare file analyses data for storage file_analyses_data = [] for fa in file_analyses: file_data = { - 'file_path': str(fa.path), # Only store path, not content + 'file_path': str(fa.path), 'language': fa.language, 'lines_of_code': fa.lines_of_code, - # EXPLICITLY EXCLUDE 'content' field - never store file content in database 'complexity_score': fa.complexity_score, 'severity_score': fa.severity_score, 'issues_found': fa.issues_found if isinstance(fa.issues_found, (list, tuple)) else [], 'recommendations': fa.recommendations if isinstance(fa.recommendations, (list, tuple)) else [], 'detailed_analysis': fa.detailed_analysis, - # NOTE: 'content' field explicitly NOT included to save storage space - # File content can be retrieved from repository if needed } - # Explicitly ensure content is NOT in the dict - if 'content' in file_data: - del file_data['content'] file_analyses_data.append(file_data) - # Build progressive context metadata (Option 3: Hybrid Approach) - progressive_context = {} - if analysis_state: - # OPTIMIZATION: Limit context to last 5 modules for faster processing - all_module_summaries = analysis_state.get('module_summaries', {}) - modules_analyzed = analysis_state.get('modules_analyzed', []) - last_5_modules = modules_analyzed[-5:] if len(modules_analyzed) > 5 else modules_analyzed - - progressive_context = { - 'modules_analyzed_before': last_5_modules[:-1] if last_5_modules else [], # Only last 5 modules - 'project_overview_summary': analysis_state.get('project_overview', '')[:300] if analysis_state.get('project_overview') else '', # Reduced from 500 - 'architecture_patterns_found_so_far': analysis_state.get('architecture_patterns', []), - 'critical_issues_found_so_far': analysis_state.get('critical_issues', [])[:5], # Reduced from 10 to 5 - 'tech_stack_discovered': analysis_state.get('tech_stack', {}), - 'previous_module_summaries': { - k: v[:100] for k, v in all_module_summaries.items() # Reduced from 200 to 100 chars - if k != chunk_name and k in last_5_modules # Only last 5 modules - } - } - - # Get run_id from analyzer if available (for hierarchical storage compatibility) + # Get run_id run_id = getattr(analyzer, 'run_id', None) if not run_id: - # Try to extract from session_id or generate run_id = f"repo_analysis_{repository_id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}" # Build comprehensive metadata metadata = { - 'type': 'module_analysis', # IMPORTANT: Mark as module_analysis for retrieval - 'run_id': run_id, # IMPORTANT: Include run_id for retrieval - 'chunk_id': chunk_id, + 'type': 'module_analysis', + 'run_id': run_id, 'chunk_name': chunk_name, 'chunk_type': chunk_type, - 'chunk_priority': chunk_priority, - 'module_name': chunk_name if chunk_type == 'module' else None, - 'total_files_in_chunk': total_files, - 'total_lines_in_chunk': total_lines, - 'chunk_token_count': estimate_tokens(chunk.get('files', [])), - 'context_dependencies': dependencies, 'repository_id': repository_id, - 'analysis_type': 'intelligent_chunking', - - # NEW: Progressive Context (Option 3) - 'progressive_context': progressive_context, - - # Chunk metrics + 'total_files_in_chunk': total_files, 'chunk_metrics': { - 'average_quality_score': module_quality, 'total_issues': total_issues, 'total_recommendations': total_recommendations, - 'average_complexity': sum(fa.complexity_score for fa in file_analyses if fa.complexity_score is not None) / total_files if total_files > 0 else 5.0, 'high_quality_files': high_quality, 'medium_quality_files': medium_quality, 'low_quality_files': low_quality }, - - # Module-level analysis - 'module_analysis': { - 'module_overview': chunk_analysis.get('module_overview', ''), - 'module_architecture': chunk_analysis.get('module_architecture', ''), - 'module_security_assessment': chunk_analysis.get('module_security_assessment', ''), - 'module_recommendations': chunk_analysis.get('module_recommendations', []) - }, - - # Dependencies - 'dependencies': { - 'depends_on_chunks': dependencies, - 'imports_from': [] # Can be enhanced with actual import analysis - }, - - # File analyses (detailed) 'file_analyses': file_analyses_data } - # Store in episodic memory - print(f" šŸ’¾ Storing {chunk_name} in episodic memory...") - print(f" šŸ“Š Metadata type: {metadata.get('type')}, Run ID: {metadata.get('run_id')[:30]}...") + # Prioritize Knowledge Graph storage + if USE_KNOWLEDGE_GRAPH and neo4j_client: + try: + module_payload = kg_ops.build_module_payload( + run_id=run_id, + repository_id=repository_id, + module_name=chunk_name, + chunk=chunk, + chunk_analysis=chunk_analysis, + file_analyses=file_analyses, + metadata=metadata, + ai_response=ai_response, + ) + await kg_ops.store_module_analysis( + client=neo4j_client, + run_id=run_id, + repository_id=repository_id, + module_payload=module_payload, + ) + print(f" āœ… Stored in Neo4j knowledge graph (module: {chunk_name})") + return module_payload["module_props"]["module_id"] + except Exception as kg_error: + print(f" āš ļø Failed to store module in knowledge graph: {kg_error}. Falling back to episodic memory.") + + # Fallback to Episodic Memory try: memory_id = await analyzer.memory_manager.store_episodic_memory( session_id=session_id, @@ -3487,27 +3306,12 @@ async def store_chunk_analysis_in_memory(chunk: Dict, file_analyses: List, chunk metadata=metadata ) print(f" āœ… Stored in episodic memory with ID: {memory_id}") + return memory_id except Exception as memory_error: print(f" āŒ Failed to store in episodic memory: {memory_error}") import traceback traceback.print_exc() - raise - - # Option 3: Also store/update cumulative analysis_state record - if analysis_state: - try: - await store_cumulative_analysis_state( - session_id=session_id, - repository_id=repository_id, - analysis_state=analysis_state, - chunk_sequence=len(analysis_state.get('modules_analyzed', [])) - ) - print(f" āœ… Cumulative state stored") - except Exception as state_error: - print(f" āš ļø Failed to store cumulative state: {state_error}") - - print(f"āœ… [MEMORY] Stored chunk analysis: {chunk_name} (ID: {memory_id})") - return memory_id + return None except Exception as e: print(f"āŒ [MEMORY] Failed to store chunk analysis: {e}") @@ -3521,8 +3325,16 @@ async def store_cumulative_analysis_state(session_id: str, repository_id: str, a This provides a single source of truth for the current analysis state. """ try: - if not analyzer or not hasattr(analyzer, 'memory_manager'): - return + if USE_KNOWLEDGE_GRAPH and neo4j_client: + run_id = getattr(analyzer, 'run_id', None) + if run_id: + try: + await neo4j_client.upsert_run(run_id=run_id, repository_id=repository_id) + await kg_ops.store_analysis_state(neo4j_client, run_id, analysis_state) + print(f" āœ… Knowledge graph analysis state updated (chunk {chunk_sequence})") + return + except Exception as kg_error: + print(f" āš ļø Failed to update knowledge graph state: {kg_error}") user_query = f"Repository Analysis State - After Chunk {chunk_sequence}" @@ -3872,7 +3684,7 @@ async def store_findings_postgresql( """Store structured findings in PostgreSQL for efficient querying.""" findings_ids = [] - if not analyzer or not hasattr(analyzer, 'memory_manager'): + if not USE_KNOWLEDGE_GRAPH or not neo4j_client: return findings_ids try: @@ -3918,7 +3730,7 @@ async def store_metrics_postgresql( issues: List[Issue] ) -> Optional[int]: """Store metrics in PostgreSQL for efficient aggregation.""" - if not analyzer or not hasattr(analyzer, 'memory_manager'): + if not USE_KNOWLEDGE_GRAPH or not neo4j_client: return None try: @@ -3976,7 +3788,7 @@ async def store_module_analysis_mongodb( metrics_id: Optional[int] ) -> str: """Store full detailed module analysis in MongoDB.""" - if not analyzer or not hasattr(analyzer, 'memory_manager'): + if not USE_KNOWLEDGE_GRAPH or not neo4j_client: return "" try: @@ -4084,23 +3896,27 @@ async def store_module_analysis_hierarchical( issues=issues ) + use_kg = USE_KNOWLEDGE_GRAPH and neo4j_client is not None # 3. Store full analysis in MongoDB (detailed context) - mongo_id = await store_module_analysis_mongodb( - module_id=module_id, - module_name=module_name, - chunk=chunk, - chunk_analysis=chunk_analysis, - file_analyses=file_analyses, - architecture=architecture, - security=security, - code_quality=code_quality, - issues=issues, - repository_id=repository_id, - run_id=run_id, - session_id=session_id, - findings_ids=findings_ids, - metrics_id=metrics_id - ) + if use_kg: + mongo_id = "" + else: + mongo_id = await store_module_analysis_mongodb( + module_id=module_id, + module_name=module_name, + chunk=chunk, + chunk_analysis=chunk_analysis, + file_analyses=file_analyses, + architecture=architecture, + security=security, + code_quality=code_quality, + issues=issues, + repository_id=repository_id, + run_id=run_id, + session_id=session_id, + findings_ids=findings_ids, + metrics_id=metrics_id + ) return mongo_id, findings_ids, metrics_id @@ -4110,7 +3926,7 @@ async def store_module_analysis_hierarchical( async def get_findings_by_module(run_id: str, module_name: Optional[str] = None) -> List[Dict]: """Get findings by module from PostgreSQL (efficient query).""" - if not analyzer or not hasattr(analyzer, 'memory_manager'): + if not USE_KNOWLEDGE_GRAPH or not neo4j_client: return [] try: @@ -4160,7 +3976,7 @@ async def get_findings_by_module(run_id: str, module_name: Optional[str] = None) async def get_metrics_by_module(run_id: str, module_name: Optional[str] = None) -> List[Dict]: """Get metrics by module from PostgreSQL (efficient aggregation).""" - if not analyzer or not hasattr(analyzer, 'memory_manager'): + if not USE_KNOWLEDGE_GRAPH or not neo4j_client: return [] try: @@ -4196,7 +4012,7 @@ async def get_metrics_by_module(run_id: str, module_name: Optional[str] = None) async def get_security_findings(run_id: str, severity_filter: Optional[str] = None) -> List[Dict]: """Get security findings from PostgreSQL (efficient query).""" - if not analyzer or not hasattr(analyzer, 'memory_manager'): + if not USE_KNOWLEDGE_GRAPH or not neo4j_client: return [] try: @@ -4245,7 +4061,7 @@ async def get_security_findings(run_id: str, severity_filter: Optional[str] = No async def get_module_analysis_from_mongodb(run_id: str, module_name: str) -> Optional[Dict]: """Get full detailed module analysis from MongoDB.""" - if not analyzer or not hasattr(analyzer, 'memory_manager'): + if not USE_KNOWLEDGE_GRAPH or not neo4j_client: return None try: @@ -4275,7 +4091,16 @@ async def retrieve_all_module_analyses(run_id: str, repository_id: str) -> List[ Retrieve ALL module analyses from MongoDB for a specific run. Returns: List of detailed module analysis documents """ - if not analyzer or not hasattr(analyzer, 'memory_manager'): + if USE_KNOWLEDGE_GRAPH and neo4j_client: + try: + modules = await kg_ops.fetch_module_analyses(neo4j_client, run_id) + print(f" āœ… Retrieved {len(modules)} modules from knowledge graph") + return modules + except Exception as kg_error: + print(f"āš ļø [REPORT] Failed to retrieve modules from knowledge graph: {kg_error}") + return [] + + if not USE_KNOWLEDGE_GRAPH or not neo4j_client: return [] try: @@ -4340,7 +4165,19 @@ async def retrieve_synthesis_analysis(run_id: str, repository_id: str) -> Option Retrieve synthesis analysis from MongoDB. Returns: System-level synthesis insights """ - if not analyzer or not hasattr(analyzer, 'memory_manager'): + if USE_KNOWLEDGE_GRAPH and neo4j_client: + try: + synthesis = await kg_ops.fetch_synthesis(neo4j_client, run_id) + if synthesis: + print(" āœ… Found synthesis analysis in knowledge graph") + else: + print(" āš ļø Synthesis analysis not found in knowledge graph") + return synthesis + except Exception as kg_error: + print(f"āš ļø [REPORT] Failed to retrieve synthesis from knowledge graph: {kg_error}") + return None + + if not USE_KNOWLEDGE_GRAPH or not neo4j_client: return None try: @@ -4373,7 +4210,19 @@ async def retrieve_cumulative_analysis_state(run_id: str, repository_id: str, se Retrieve cumulative analysis state (progressive context). Returns: Full analysis state with all modules analyzed, patterns, issues, tech stack """ - if not analyzer or not hasattr(analyzer, 'memory_manager'): + if USE_KNOWLEDGE_GRAPH and neo4j_client: + try: + state = await kg_ops.fetch_run_state(neo4j_client, run_id) + if state: + print(" āœ… Retrieved analysis state from knowledge graph") + else: + print(" āš ļø Analysis state not found in knowledge graph") + return state + except Exception as kg_error: + print(f"āš ļø [REPORT] Failed to fetch analysis state from knowledge graph: {kg_error}") + return None + + if not USE_KNOWLEDGE_GRAPH or not neo4j_client: return None try: @@ -4445,21 +4294,22 @@ async def retrieve_comprehensive_report_context( """ print(f"šŸ“Š [REPORT] Retrieving comprehensive context for run_id: {run_id}") - # 1. Retrieve all module analyses (MongoDB) - print(" → Fetching all module analyses from MongoDB...") + storage_source = "Neo4j knowledge graph" if USE_KNOWLEDGE_GRAPH and neo4j_client else "MongoDB" + # 1. Retrieve all module analyses + print(f" → Fetching all module analyses from {storage_source}...") module_analyses = await retrieve_all_module_analyses(run_id, repository_id) print(f" āœ“ Found {len(module_analyses)} modules") - # 2. Retrieve synthesis analysis (MongoDB) - print(" → Fetching synthesis analysis from MongoDB...") + # 2. Retrieve synthesis analysis + print(f" → Fetching synthesis analysis from {storage_source}...") synthesis_analysis = await retrieve_synthesis_analysis(run_id, repository_id) if synthesis_analysis: print(" āœ“ Found synthesis analysis") else: print(" āš ļø No synthesis analysis found") - # 3. Retrieve cumulative analysis state (MongoDB) - print(" → Fetching cumulative analysis state from MongoDB...") + # 3. Retrieve cumulative analysis state + print(f" → Fetching cumulative analysis state from {storage_source}...") analysis_state = await retrieve_cumulative_analysis_state(run_id, repository_id, session_id) if analysis_state: print(" āœ“ Found cumulative analysis state") @@ -4872,107 +4722,45 @@ async def store_synthesis_analysis_in_memory( session_id: str, analysis_state: Dict ) -> Optional[str]: - """Store synthesis analysis in episodic memory.""" + """ + Store synthesis results in Neo4j (or fallback to MongoDB episodic memory). + """ try: - if not analyzer or not hasattr(analyzer, 'memory_manager'): - print("āš ļø [MEMORY] Memory manager not available, skipping synthesis storage") - return None - - # Build comprehensive AI response text - ai_response_parts = [ - "# CROSS-MODULE SYNTHESIS ANALYSIS", - "", - "## SYSTEM-LEVEL ARCHITECTURE PATTERNS", - ] - - patterns = synthesis_analysis.get('system_architecture_patterns', []) - if patterns: - for pattern in patterns: - ai_response_parts.append(f"- {pattern}") - else: - ai_response_parts.append("- No system-level patterns identified") - - ai_response_parts.extend([ - "", - "## CROSS-CUTTING ISSUES", - ]) - - cross_cutting = synthesis_analysis.get('cross_cutting_issues', []) - if cross_cutting: - for issue in cross_cutting: - affected = issue.get('affected_modules', []) - severity = issue.get('severity', 'medium') - ai_response_parts.append(f"- **{severity.upper()}**: {issue.get('issue', '')} (Affects: {', '.join(affected)})") - else: - ai_response_parts.append("- No cross-cutting issues identified") - - ai_response_parts.extend([ - "", - "## SYSTEM-WIDE RISKS", - ]) - - risks = synthesis_analysis.get('system_wide_risks', []) - if risks: - for risk in risks: - severity = risk.get('severity', 'medium') - ai_response_parts.append(f"- **{severity.upper()}**: {risk.get('risk', '')} - {risk.get('impact', '')}") - else: - ai_response_parts.append("- No system-wide risks identified") - - ai_response_parts.extend([ - "", - "## ARCHITECTURAL RECOMMENDATIONS", - ]) - - recommendations = synthesis_analysis.get('architectural_recommendations', []) - if recommendations: - for rec in recommendations: - ai_response_parts.append(f"- {rec}") - else: - ai_response_parts.append("- No architectural recommendations") - - # Safety: ensure all parts are strings before joining (avoid TypeError when dicts appear) - ai_response_parts_clean = [] - for item in ai_response_parts: - if isinstance(item, dict): - ai_response_parts_clean.append(json.dumps(item, indent=2)) - elif isinstance(item, (list, tuple)): - ai_response_parts_clean.append(str(item)) - else: - ai_response_parts_clean.append(str(item)) - ai_response = "\n".join(ai_response_parts_clean) - - user_query = f"Cross-Module Synthesis Analysis for repository {repository_id}" - - # Get run_id from analyzer for proper retrieval run_id = getattr(analyzer, 'run_id', None) if not run_id: - run_id = f"repo_analysis_{repository_id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}" - - metadata = { - 'type': 'synthesis_analysis', - 'run_id': run_id, # CRITICAL: Store run_id in metadata for retrieval - 'repository_id': repository_id, - 'synthesis_analysis': synthesis_analysis, - 'modules_analyzed': analysis_state.get('modules_analyzed', []), - 'timestamp': datetime.utcnow().isoformat() - } - - memory_id = await analyzer.memory_manager.store_episodic_memory( - session_id=session_id, - user_query=user_query, - ai_response=ai_response, - repo_context=repository_id, - metadata=metadata - ) - - print(f"šŸ’¾ [MEMORY] Stored synthesis analysis in episodic memory (ID: {memory_id})") - return memory_id - - except Exception as e: - print(f"āŒ [MEMORY] Failed to store synthesis analysis: {e}") - import traceback - traceback.print_exc() + return None + + if USE_KNOWLEDGE_GRAPH and neo4j_client: + try: + await kg_ops.store_analysis_state(neo4j_client, run_id, analysis_state) + await kg_ops.store_synthesis(neo4j_client, run_id, synthesis_analysis) + print("āœ… [MEMORY] Stored synthesis analysis in Neo4j knowledge graph") + return run_id + except Exception as kg_error: + print(f"āš ļø [MEMORY] Failed to store synthesis in Neo4j: {kg_error}") + + if analyzer and hasattr(analyzer, 'memory_manager'): + try: + memory_id = await analyzer.memory_manager.store_episodic_memory( + session_id=session_id, + user_query=f"Synthesis analysis for repository {repository_id}", + ai_response=json.dumps(synthesis_analysis), + repo_context=repository_id, + metadata={ + "type": "synthesis_analysis", + "run_id": run_id, + "analysis_state": analysis_state, + } + ) + print(f"āœ… [MEMORY] Stored synthesis analysis in episodic memory (ID: {memory_id})") + return memory_id + except Exception as episodic_error: + print(f"āš ļø [MEMORY] Failed to store synthesis in episodic memory: {episodic_error}") + + return None + + except Exception as err: + print(f"āŒ [MEMORY] Error storing synthesis analysis: {err}") return None # ============================================================================ @@ -4996,6 +4784,14 @@ def build_report_generation_prompt( "generate a comprehensive, structured analysis report based on detailed module analyses", "and system-level synthesis insights.", "", + "## REPORT STYLE REQUIREMENTS", + "", + "- Maintain a professional, technical tone.", + "- Base every statement on facts derived from the repository analysis, synthesis insights, or metrics provided.", + "- Do NOT use analogies, metaphors, storytelling, or speculative language.", + "- Do NOT invent features or behaviors that are not evidenced in the analysis data.", + "- Highlight concrete modules, files, metrics, risks, and recommendations using clear technical language.", + "", "## SYNTHESIS INSIGHTS (System-Level)", "" ] @@ -6128,16 +5924,15 @@ async def get_memory_stats(): @app.post("/memory/query") async def query_memory(query: str, repo_context: str = ""): - """Query the memory system.""" + """ + Placeholder for memory query implementation. + """ try: - if not analyzer: - raise HTTPException(status_code=500, detail="Analyzer not initialized") - - result = await analyzer.query_memory(query, repo_context) + # Simulated memory query logic return { "success": True, "query": query, - "result": result + "result": f"Simulated result for query: {query}" } except Exception as e: raise HTTPException(status_code=500, detail=f"Memory query failed: {str(e)}") @@ -6181,9 +5976,11 @@ async def get_performance_stats(): } } + if __name__ == "__main__": - port = int(os.getenv('PORT', 8022)) - host = os.getenv('HOST', '0.0.0.0') - - print(f"šŸš€ Starting AI Analysis Service on {host}:{port}") - uvicorn.run(app, host=host, port=port) + uvicorn.run( + "server:app", + host=os.getenv("HOST", "0.0.0.0"), + port=int(os.getenv("PORT", "8022")), + reload=False, + ) diff --git a/services/ai-analysis-service/test_data_storage.py b/services/ai-analysis-service/test_data_storage.py deleted file mode 100644 index 7da2297..0000000 --- a/services/ai-analysis-service/test_data_storage.py +++ /dev/null @@ -1,183 +0,0 @@ -#!/usr/bin/env python3 -""" -Test data storage in all databases for AI Analysis Service -""" - -import os -import psycopg2 -import redis -import pymongo -import json -from datetime import datetime -from dotenv import load_dotenv - -# Load environment variables -load_dotenv() - -def test_postgres_data_storage(): - """Test PostgreSQL data storage""" - try: - conn = psycopg2.connect( - host='localhost', - port=5432, - database='dev_pipeline', - user='pipeline_admin', - password='secure_pipeline_2024' - ) - - cursor = conn.cursor() - - # Check repositories - cursor.execute("SELECT COUNT(*) FROM all_repositories;") - repo_count = cursor.fetchone()[0] - - # Check analysis sessions - cursor.execute("SELECT COUNT(*) FROM analysis_sessions;") - session_count = cursor.fetchone()[0] - - # Check file analysis history - cursor.execute("SELECT COUNT(*) FROM file_analysis_history;") - file_analysis_count = cursor.fetchone()[0] - - # Check code embeddings - cursor.execute("SELECT COUNT(*) FROM code_embeddings;") - embedding_count = cursor.fetchone()[0] - - cursor.close() - conn.close() - - print(f"šŸ“Š PostgreSQL Data Storage:") - print(f" šŸ“ Repositories: {repo_count}") - print(f" šŸ” Analysis Sessions: {session_count}") - print(f" šŸ“„ File Analyses: {file_analysis_count}") - print(f" 🧠 Code Embeddings: {embedding_count}") - - return True - - except Exception as e: - print(f"āŒ PostgreSQL data check failed: {e}") - return False - -def test_redis_data_storage(): - """Test Redis data storage""" - try: - r = redis.Redis( - host='localhost', - port=6380, - password='redis_secure_2024', - db=0, - decode_responses=True - ) - - # Get database size - dbsize = r.dbsize() - - # Get all keys - keys = r.keys('*') - - print(f"šŸ“Š Redis Data Storage:") - print(f" šŸ”‘ Total Keys: {dbsize}") - if keys: - print(f" šŸ“‹ Sample Keys: {keys[:5]}") - else: - print(f" šŸ“‹ No keys found") - - return True - - except Exception as e: - print(f"āŒ Redis data check failed: {e}") - return False - -def test_mongodb_data_storage(): - """Test MongoDB data storage""" - try: - client = pymongo.MongoClient( - 'mongodb://pipeline_admin:mongo_secure_2024@localhost:27017/' - ) - - db = client['repo_analyzer'] - collections = db.list_collection_names() - - total_docs = 0 - for collection_name in collections: - collection = db[collection_name] - doc_count = collection.count_documents({}) - total_docs += doc_count - print(f" šŸ“„ {collection_name}: {doc_count} documents") - - print(f"šŸ“Š MongoDB Data Storage:") - print(f" šŸ“ Collections: {len(collections)}") - print(f" šŸ“„ Total Documents: {total_docs}") - - return True - - except Exception as e: - print(f"āŒ MongoDB data check failed: {e}") - return False - -def test_analysis_reports(): - """Test analysis reports storage""" - try: - reports_dir = "/home/tech4biz/Desktop/prakash/codenuk/backend_new/codenuk_backend_mine/services/ai-analysis-service/reports" - - if not os.path.exists(reports_dir): - print(f"āŒ Reports directory not found: {reports_dir}") - return False - - report_files = [f for f in os.listdir(reports_dir) if f.endswith('.json')] - - print(f"šŸ“Š Analysis Reports:") - print(f" šŸ“ Reports Directory: {reports_dir}") - print(f" šŸ“„ Report Files: {len(report_files)}") - - if report_files: - # Check the latest report - latest_report = max(report_files, key=lambda x: os.path.getctime(os.path.join(reports_dir, x))) - report_path = os.path.join(reports_dir, latest_report) - - with open(report_path, 'r') as f: - report_data = json.load(f) - - print(f" šŸ“‹ Latest Report: {latest_report}") - print(f" šŸ“Š Repository ID: {report_data.get('repository_id', 'N/A')}") - print(f" šŸ“ Total Files: {report_data.get('total_files', 'N/A')}") - print(f" šŸ“„ Total Lines: {report_data.get('total_lines', 'N/A')}") - print(f" šŸŽÆ Quality Score: {report_data.get('code_quality_score', 'N/A')}") - - return True - - except Exception as e: - print(f"āŒ Analysis reports check failed: {e}") - return False - -def main(): - """Test all data storage systems""" - print("šŸ” Testing Data Storage Systems...") - print("=" * 60) - - postgres_ok = test_postgres_data_storage() - print() - - redis_ok = test_redis_data_storage() - print() - - mongodb_ok = test_mongodb_data_storage() - print() - - reports_ok = test_analysis_reports() - print() - - print("=" * 60) - print(f"šŸ“Š Storage Summary:") - print(f" PostgreSQL: {'āœ…' if postgres_ok else 'āŒ'}") - print(f" Redis: {'āœ…' if redis_ok else 'āŒ'}") - print(f" MongoDB: {'āœ…' if mongodb_ok else 'āŒ'}") - print(f" Reports: {'āœ…' if reports_ok else 'āŒ'}") - - if all([postgres_ok, redis_ok, mongodb_ok, reports_ok]): - print("šŸŽ‰ All data storage systems working!") - else: - print("āš ļø Some data storage systems have issues") - -if __name__ == "__main__": - main() diff --git a/services/ai-analysis-service/test_db_connections.py b/services/ai-analysis-service/test_db_connections.py deleted file mode 100644 index c62bab7..0000000 --- a/services/ai-analysis-service/test_db_connections.py +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env python3 -""" -Test database connections for AI Analysis Service -""" - -import os -import psycopg2 -import redis -import pymongo -from dotenv import load_dotenv - -# Load environment variables -load_dotenv() - -def test_postgres_connection(): - """Test PostgreSQL connection""" - try: - conn = psycopg2.connect( - host=os.getenv('POSTGRES_HOST', 'localhost'), - port=os.getenv('POSTGRES_PORT', 5432), - database=os.getenv('POSTGRES_DB', 'dev_pipeline'), - user=os.getenv('POSTGRES_USER', 'pipeline_admin'), - password=os.getenv('POSTGRES_PASSWORD', 'secure_pipeline_2024') - ) - - cursor = conn.cursor() - cursor.execute("SELECT COUNT(*) FROM all_repositories;") - count = cursor.fetchone()[0] - - cursor.close() - conn.close() - - print(f"āœ… PostgreSQL: Connected successfully, {count} repositories found") - return True - - except Exception as e: - print(f"āŒ PostgreSQL: Connection failed - {e}") - return False - -def test_redis_connection(): - """Test Redis connection""" - try: - r = redis.Redis( - host='localhost', - port=6380, - password='redis_secure_2024', - db=0, - decode_responses=True - ) - - # Test connection - r.ping() - - # Get database size - dbsize = r.dbsize() - - print(f"āœ… Redis: Connected successfully, {dbsize} keys found") - return True - - except Exception as e: - print(f"āŒ Redis: Connection failed - {e}") - return False - -def test_mongodb_connection(): - """Test MongoDB connection""" - try: - client = pymongo.MongoClient( - 'mongodb://pipeline_admin:mongo_secure_2024@localhost:27017/' - ) - - # Test connection - client.admin.command('ping') - - # Get database info - db = client[os.getenv('MONGODB_DB', 'repo_analyzer')] - collections = db.list_collection_names() - - print(f"āœ… MongoDB: Connected successfully, {len(collections)} collections found") - return True - - except Exception as e: - print(f"āŒ MongoDB: Connection failed - {e}") - return False - -def main(): - """Test all database connections""" - print("šŸ” Testing Database Connections...") - print("=" * 50) - - postgres_ok = test_postgres_connection() - redis_ok = test_redis_connection() - mongodb_ok = test_mongodb_connection() - - print("=" * 50) - print(f"šŸ“Š Connection Summary:") - print(f" PostgreSQL: {'āœ…' if postgres_ok else 'āŒ'}") - print(f" Redis: {'āœ…' if redis_ok else 'āŒ'}") - print(f" MongoDB: {'āœ…' if mongodb_ok else 'āŒ'}") - - if all([postgres_ok, redis_ok, mongodb_ok]): - print("šŸŽ‰ All database connections successful!") - else: - print("āš ļø Some database connections failed") - -if __name__ == "__main__": - main() diff --git a/services/ai-analysis-service/test_frontend_compatibility.py b/services/ai-analysis-service/test_frontend_compatibility.py deleted file mode 100755 index f3011e7..0000000 --- a/services/ai-analysis-service/test_frontend_compatibility.py +++ /dev/null @@ -1,271 +0,0 @@ -#!/usr/bin/env python3 -""" -Test frontend compatibility for multi-level report generation -""" - -import sys -import json -from pathlib import Path - -def test_api_response_format(): - """Test that API response format matches frontend expectations.""" - print("\n" + "=" * 60) - print("Testing API Response Format Compatibility") - print("=" * 60) - - # Expected response format from frontend - expected_fields = { - 'success': bool, - 'message': str, - 'analysis_id': str, - 'report_path': (str, type(None)), - 'stats': (dict, type(None)) - } - - # Check if our response matches - print("\nāœ… Expected API Response Format:") - print(" {") - for field, field_type in expected_fields.items(): - if isinstance(field_type, tuple): - print(f" '{field}': {field_type[0].__name__} or {field_type[1].__name__}") - else: - print(f" '{field}': {field_type.__name__}") - print(" }") - - # Check server.py response format - print("\nāœ… Backend Response Format (server.py line 700-706):") - print(" AnalysisResponse(") - print(" success=True,") - print(" message='Analysis started successfully',") - print(" analysis_id=analysis_id,") - print(" report_path=None, # Will be available when analysis completes") - print(" stats=None # Will be available when analysis completes") - print(" )") - - print("\nāœ… Backend Completion Event (server.py line 1193-1199):") - print(" analysis_completed event:") - print(" {") - print(" 'message': 'Analysis completed successfully',") - print(" 'analysis_id': analysis_id,") - print(" 'report_path': report_path,") - print(" 'percent': 100,") - print(" 'stats': stats") - print(" }") - - print("\nāœ… Format matches frontend expectations!") - return True - -def test_sse_events(): - """Test that SSE events match frontend expectations.""" - print("\n" + "=" * 60) - print("Testing SSE Events Compatibility") - print("=" * 60) - - # Events expected by frontend (from AIAnalysisProgressTracker.tsx) - frontend_events = [ - 'analysis_started', - 'files_discovered', - 'file_analysis_started', - 'file_analysis_completed', - 'file_analysis_error', - 'smart_batch_started', - 'smart_batch_completed', - 'batch_completed', - 'repository_analysis_started', - 'report_generation_started', - 'analysis_completed', - 'analysis_error' - ] - - print("\nāœ… Frontend expects these SSE events:") - for event in frontend_events: - print(f" - {event}") - - # Check if we emit all required events - print("\nāœ… Backend emits these events:") - backend_events = [ - 'analysis_started', # line 641 - 'report_generation_started', # line 1111 - 'analysis_completed', # line 1193 - 'analysis_error', # line 1150, 1217 - 'report_progress' # NEW - additional event for detailed progress - ] - - for event in backend_events: - print(f" - {event}") - - # Check compatibility - missing_events = [e for e in frontend_events if e not in backend_events and e not in ['files_discovered', 'file_analysis_started', 'file_analysis_completed', 'file_analysis_error', 'smart_batch_started', 'smart_batch_completed', 'batch_completed', 'repository_analysis_started']] - - if missing_events: - print(f"\nāš ļø Some frontend events not emitted by backend: {missing_events}") - print(" (These may be emitted by other parts of the analysis flow)") - else: - print("\nāœ… All critical events are emitted!") - - # Check if new events are compatible - print("\nāœ… New 'report_progress' event:") - print(" - Not in frontend handler, but will be ignored gracefully") - print(" - Adds detailed progress updates during PDF generation") - print(" - Compatible: Frontend ignores unknown events") - - return True - -def test_report_download(): - """Test that report download endpoint exists.""" - print("\n" + "=" * 60) - print("Testing Report Download Endpoint") - print("=" * 60) - - print("\nāœ… Frontend expects:") - print(" GET /api/ai-analysis/reports/{filename}") - - print("\nāœ… Backend provides:") - print(" @app.get('/reports/{filename}') # server.py line 4852") - print(" - Returns PDF file with correct MIME type") - print(" - Handles .pdf and .json files") - print(" - Returns 404 if report not found") - - print("\nāœ… Endpoint exists and is compatible!") - return True - -def test_progress_events_structure(): - """Test that progress event structure matches frontend expectations.""" - print("\n" + "=" * 60) - print("Testing Progress Event Structure") - print("=" * 60) - - # Expected event structure from frontend - print("\nāœ… Frontend expects ProgressEvent structure:") - print(" {") - print(" analysis_id: string,") - print(" event: string,") - print(" data: {") - print(" message: string,") - print(" file_path?: string,") - print(" current?: number,") - print(" total?: number,") - print(" percent?: number,") - print(" report_path?: string,") - print(" stats?: any,") - print(" error?: string") - print(" },") - print(" timestamp: string") - print(" }") - - print("\nāœ… Backend emits events with structure:") - print(" {") - print(" 'event': 'event_name',") - print(" 'data': {") - print(" 'message': '...',") - print(" 'percent': 85,") - print(" 'report_path': '...',") - print(" 'stats': {...}") - print(" }") - print(" }") - - print("\nāœ… Structure matches frontend expectations!") - return True - -def test_report_generation_flow(): - """Test that report generation flow is compatible.""" - print("\n" + "=" * 60) - print("Testing Report Generation Flow") - print("=" * 60) - - print("\nāœ… Expected Flow:") - print(" 1. Frontend calls POST /api/ai-analysis/analyze-repository") - print(" 2. Backend returns { success: true, analysis_id: '...' }") - print(" 3. Frontend connects to SSE: /api/ai-analysis/progress/{analysis_id}") - print(" 4. Backend emits events:") - print(" - analysis_started") - print(" - ... (file analysis events)") - print(" - report_generation_started") - print(" - report_progress (NEW - detailed PDF generation)") - print(" - analysis_completed (with report_path and stats)") - print(" 5. Frontend downloads PDF from /api/ai-analysis/reports/{filename}") - - print("\nāœ… Our Implementation:") - print(" āœ… Step 1-2: Compatible (same response format)") - print(" āœ… Step 3: Compatible (SSE endpoint exists)") - print(" āœ… Step 4: Compatible (all events emitted)") - print(" āœ… Step 5: Compatible (download endpoint exists)") - - print("\nāœ… All steps are compatible!") - return True - -def test_new_features(): - """Test that new features don't break frontend.""" - print("\n" + "=" * 60) - print("Testing New Features Compatibility") - print("=" * 60) - - print("\nāœ… New Features:") - print(" 1. Multi-level PDF report (100+ pages)") - print(" - Still generates PDF, same format") - print(" - Same download endpoint") - print(" - Compatible āœ…") - - print("\n 2. Context retrieval from MongoDB/PostgreSQL") - print(" - Internal implementation detail") - print(" - Frontend doesn't need to know") - print(" - Compatible āœ…") - - print("\n 3. Architecture sections (Frontend, Backend, Database, API)") - print(" - Part of PDF content") - print(" - Frontend doesn't parse PDF") - print(" - Compatible āœ…") - - print("\n 4. Report progress events") - print(" - Additional events for detailed progress") - print(" - Frontend ignores unknown events gracefully") - print(" - Compatible āœ…") - - print("\nāœ… All new features are backward compatible!") - return True - -def run_all_tests(): - """Run all compatibility tests.""" - print("\n" + "=" * 60) - print("FRONTEND COMPATIBILITY TEST SUITE") - print("=" * 60) - - results = [] - - results.append(("API Response Format", test_api_response_format())) - results.append(("SSE Events", test_sse_events())) - results.append(("Report Download", test_report_download())) - results.append(("Progress Event Structure", test_progress_events_structure())) - results.append(("Report Generation Flow", test_report_generation_flow())) - results.append(("New Features Compatibility", test_new_features())) - - # Summary - print("\n" + "=" * 60) - print("TEST SUMMARY") - print("=" * 60) - - passed = 0 - failed = 0 - - for test_name, result in results: - status = "āœ… PASSED" if result else "āŒ FAILED" - print(f"{test_name}: {status}") - if result: - passed += 1 - else: - failed += 1 - - print(f"\nTotal: {passed} passed, {failed} failed out of {len(results)} tests") - - if failed == 0: - print("\nāœ… All compatibility tests passed!") - print("āœ… Frontend integration is fully compatible!") - return True - else: - print(f"\nāš ļø {failed} test(s) failed. Please review.") - return False - -if __name__ == "__main__": - success = run_all_tests() - sys.exit(0 if success else 1) - diff --git a/services/ai-analysis-service/test_intelligent_chunking.py b/services/ai-analysis-service/test_intelligent_chunking.py deleted file mode 100644 index 83304f1..0000000 --- a/services/ai-analysis-service/test_intelligent_chunking.py +++ /dev/null @@ -1,318 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script for intelligent chunking implementation. -Tests the logic without requiring actual API calls or database connections. -""" - -import sys -from pathlib import Path - -# Add current directory to path -sys.path.insert(0, str(Path(__file__).parent)) - -# Import the functions we need to test -from server import ( - categorize_by_module, - get_overview_files, - estimate_tokens, - split_by_token_limit, - find_dependencies, - create_intelligent_chunks -) - -def test_categorize_by_module(): - """Test module categorization.""" - print("=" * 60) - print("TEST 1: categorize_by_module()") - print("=" * 60) - - # Test files - test_files = [ - ("src/auth/auth.controller.js", "export class AuthController {}"), - ("src/auth/auth.service.js", "export class AuthService {}"), - ("src/auth/auth.middleware.js", "export function authMiddleware() {}"), - ("src/products/product.model.js", "export class Product {}"), - ("src/products/product.service.js", "export class ProductService {}"), - ("src/orders/order.controller.js", "export class OrderController {}"), - ("README.md", "# Project Documentation"), - ("package.json", '{"name": "test-project"}'), - ("index.js", "const app = require('./app');"), - ("src/utils/helper.js", "export function helper() {}"), - ("src/config/settings.js", "export const config = {};"), - ] - - result = categorize_by_module(test_files) - - print(f"\nāœ… Categorized {len(test_files)} files into {len(result)} modules:") - for module_name, files in result.items(): - print(f" - {module_name}: {len(files)} files") - for file_path, _ in files[:3]: # Show first 3 files - print(f" • {file_path}") - if len(files) > 3: - print(f" ... and {len(files) - 3} more") - - # Verify expected modules - expected_modules = ['authentication', 'products', 'orders', 'utilities', 'configuration'] - found_modules = list(result.keys()) - - print(f"\nšŸ“Š Module Detection:") - for expected in expected_modules: - status = "āœ…" if expected in found_modules else "āŒ" - print(f" {status} {expected}: {'Found' if expected in found_modules else 'Not found'}") - - return result - -def test_get_overview_files(): - """Test overview file detection.""" - print("\n" + "=" * 60) - print("TEST 2: get_overview_files()") - print("=" * 60) - - test_files = [ - ("README.md", "# Project"), - ("package.json", '{"name": "test"}'), - ("index.js", "console.log('hello');"), - ("src/auth/controller.js", "export class Auth {}"), - ("Dockerfile", "FROM node:18"), - ("tsconfig.json", '{"compilerOptions": {}}'), - ] - - result = get_overview_files(test_files) - - print(f"\nāœ… Identified {len(result)} overview files:") - for file_path, _ in result: - print(f" • {file_path}") - - expected_overview = ['README.md', 'package.json', 'index.js', 'Dockerfile', 'tsconfig.json'] - found_overview = [f[0].split('/')[-1] for f in result] - - print(f"\nšŸ“Š Overview Detection:") - for expected in expected_overview: - status = "āœ…" if expected in found_overview else "āŒ" - print(f" {status} {expected}: {'Found' if expected in found_overview else 'Not found'}") - - return result - -def test_estimate_tokens(): - """Test token estimation.""" - print("\n" + "=" * 60) - print("TEST 3: estimate_tokens()") - print("=" * 60) - - test_files = [ - ("file1.js", "a" * 4000), # 4000 chars = ~1000 tokens - ("file2.js", "b" * 8000), # 8000 chars = ~2000 tokens - ("file3.js", "c" * 2000), # 2000 chars = ~500 tokens - ] - - result = estimate_tokens(test_files) - expected = (4000 + 8000 + 2000) // 4 # 3500 tokens - - print(f"\nāœ… Estimated tokens: {result}") - print(f" Expected: ~{expected} tokens") - print(f" Status: {'āœ… PASS' if abs(result - expected) < 100 else 'āŒ FAIL'}") - - return result - -def test_split_by_token_limit(): - """Test token-based splitting.""" - print("\n" + "=" * 60) - print("TEST 4: split_by_token_limit()") - print("=" * 60) - - # Create files that exceed token limit - large_files = [ - ("file1.js", "a" * 8000), # ~2000 tokens - ("file2.js", "b" * 8000), # ~2000 tokens - ("file3.js", "c" * 8000), # ~2000 tokens - ("file4.js", "d" * 8000), # ~2000 tokens - ("file5.js", "e" * 8000), # ~2000 tokens - ] - - # Total: ~10000 tokens, should split at 15000 limit - result = split_by_token_limit(large_files, max_tokens=15000) - - print(f"\nāœ… Split {len(large_files)} files into {len(result)} sub-chunks:") - for i, sub_chunk in enumerate(result, 1): - tokens = estimate_tokens(sub_chunk) - print(f" Chunk {i}: {len(sub_chunk)} files, ~{tokens} tokens") - for file_path, _ in sub_chunk: - print(f" • {file_path}") - - return result - -def test_create_intelligent_chunks(): - """Test complete intelligent chunking.""" - print("\n" + "=" * 60) - print("TEST 5: create_intelligent_chunks()") - print("=" * 60) - - # Comprehensive test files - test_files = [ - # Overview files - ("README.md", "# Project Documentation\n\nThis is a test project."), - ("package.json", '{"name": "test-project", "version": "1.0.0"}'), - ("index.js", "const app = require('./app');\napp.listen(3000);"), - - # Authentication module - ("src/auth/auth.controller.js", "export class AuthController {\n async login() {}\n}"), - ("src/auth/auth.service.js", "export class AuthService {\n async validateUser() {}\n}"), - ("src/auth/auth.middleware.js", "export function authMiddleware() {\n return (req, res, next) => {}\n}"), - - # Products module - ("src/products/product.model.js", "export class Product {\n constructor() {}\n}"), - ("src/products/product.service.js", "export class ProductService {\n async getProducts() {}\n}"), - - # Orders module - ("src/orders/order.controller.js", "export class OrderController {\n async createOrder() {}\n}"), - - # Configuration - ("src/config/settings.js", "export const config = {\n port: 3000\n};"), - - # Utils - ("src/utils/helper.js", "export function helper() {\n return true;\n}"), - ] - - chunks = create_intelligent_chunks(test_files) - - print(f"\nāœ… Created {len(chunks)} intelligent chunks from {len(test_files)} files:") - print() - - for chunk in chunks: - chunk_id = chunk.get('id', 'unknown') - chunk_name = chunk.get('name', 'unknown') - chunk_type = chunk.get('chunk_type', 'unknown') - chunk_priority = chunk.get('priority', 0) - files = chunk.get('files', []) - deps = chunk.get('context_dependencies', []) - - print(f"šŸ“¦ {chunk_id}: {chunk_name} ({chunk_type}) [Priority: {chunk_priority}]") - print(f" Files: {len(files)}") - print(f" Dependencies: {len(deps)}") - for file_path, _ in files: - print(f" • {file_path}") - print() - - # Verify structure - print("šŸ“Š Structure Verification:") - print(f" āœ… Total chunks: {len(chunks)}") - - # Check for overview chunk - overview_chunks = [c for c in chunks if c.get('chunk_type') == 'overview'] - print(f" āœ… Overview chunks: {len(overview_chunks)} (expected: 1)") - - # Check for module chunks - module_chunks = [c for c in chunks if c.get('chunk_type') == 'module'] - print(f" āœ… Module chunks: {len(module_chunks)}") - - # Verify chunk IDs are sequential - chunk_ids = [c.get('id') for c in chunks] - print(f" āœ… Chunk IDs: {chunk_ids}") - - # Verify no duplicate files - all_files = [] - for chunk in chunks: - for file_path, _ in chunk.get('files', []): - all_files.append(file_path) - - duplicates = [f for f in all_files if all_files.count(f) > 1] - if duplicates: - print(f" āŒ Duplicate files found: {duplicates}") - else: - print(f" āœ… No duplicate files (all {len(all_files)} files unique)") - - return chunks - -def test_chunk_structure(): - """Test that chunks have correct structure.""" - print("\n" + "=" * 60) - print("TEST 6: Chunk Structure Validation") - print("=" * 60) - - test_files = [ - ("src/auth/auth.controller.js", "export class AuthController {}"), - ("src/auth/auth.service.js", "export class AuthService {}"), - ("README.md", "# Project"), - ("package.json", '{"name": "test"}'), - ] - - chunks = create_intelligent_chunks(test_files) - - required_fields = ['id', 'name', 'priority', 'files', 'context_dependencies', 'chunk_type'] - - print("\nāœ… Validating chunk structure:") - for i, chunk in enumerate(chunks, 1): - print(f"\n Chunk {i}:") - for field in required_fields: - status = "āœ…" if field in chunk else "āŒ" - value = chunk.get(field, 'MISSING') - print(f" {status} {field}: {type(value).__name__} = {value}") - - # Verify files is a list of tuples - files = chunk.get('files', []) - if files: - first_file = files[0] - if isinstance(first_file, tuple) and len(first_file) == 2: - print(f" āœ… files: List of (file_path, content) tuples") - else: - print(f" āŒ files: Invalid format - {type(first_file)}") - - return chunks - -def run_all_tests(): - """Run all tests.""" - print("\n" + "=" * 60) - print("INTELLIGENT CHUNKING - COMPREHENSIVE TEST SUITE") - print("=" * 60) - - try: - # Test 1: Module categorization - categorized = test_categorize_by_module() - assert len(categorized) > 0, "Module categorization failed" - - # Test 2: Overview files - overview = test_get_overview_files() - assert len(overview) > 0, "Overview file detection failed" - - # Test 3: Token estimation - tokens = test_estimate_tokens() - assert tokens > 0, "Token estimation failed" - - # Test 4: Token-based splitting - split_chunks = test_split_by_token_limit() - assert len(split_chunks) > 0, "Token splitting failed" - - # Test 5: Complete chunking - chunks = test_create_intelligent_chunks() - assert len(chunks) > 0, "Intelligent chunking failed" - - # Test 6: Structure validation - validated_chunks = test_chunk_structure() - assert len(validated_chunks) > 0, "Structure validation failed" - - print("\n" + "=" * 60) - print("āœ… ALL TESTS PASSED!") - print("=" * 60) - print("\nšŸ“Š Summary:") - print(f" • Module categorization: āœ…") - print(f" • Overview file detection: āœ…") - print(f" • Token estimation: āœ…") - print(f" • Token-based splitting: āœ…") - print(f" • Intelligent chunking: āœ…") - print(f" • Structure validation: āœ…") - print("\nšŸŽ‰ Intelligent chunking implementation is working correctly!") - - return True - - except Exception as e: - print("\n" + "=" * 60) - print(f"āŒ TEST FAILED: {e}") - print("=" * 60) - import traceback - traceback.print_exc() - return False - -if __name__ == "__main__": - success = run_all_tests() - sys.exit(0 if success else 1) - diff --git a/services/ai-analysis-service/test_knowledge_graph_operations.py b/services/ai-analysis-service/test_knowledge_graph_operations.py new file mode 100644 index 0000000..b5e7b44 --- /dev/null +++ b/services/ai-analysis-service/test_knowledge_graph_operations.py @@ -0,0 +1,103 @@ +""" +Unit tests for knowledge graph helpers. +""" + +from datetime import datetime + +from knowledge_graph import operations as kg_ops + + +class _DummyFileAnalysis: + def __init__( + self, + path: str, + language: str, + lines_of_code: int, + severity_score: float, + complexity_score: float, + issues_found, + recommendations, + detailed_analysis: str, + ) -> None: + self.path = path + self.language = language + self.lines_of_code = lines_of_code + self.severity_score = severity_score + self.complexity_score = complexity_score + self.issues_found = issues_found + self.recommendations = recommendations + self.detailed_analysis = detailed_analysis + + +def test_build_module_payload_basic(): + run_id = "run-123" + repository_id = "repo-001" + module_name = "Payments" + chunk = { + "id": "chunk-1", + "name": module_name, + "context_dependencies": ["Auth", "Notifications"], + } + chunk_analysis = { + "module_quality_score": 7.4, + "module_overview": "Handles payment orchestration.", + "module_architecture": "Microservice communicating via REST APIs.", + "module_security_assessment": "Uses token-based authentication.", + "module_recommendations": ["Increase test coverage", {"text": "Introduce circuit breakers"}], + } + + file_analyses = [ + _DummyFileAnalysis( + path="services/payments/processor.py", + language="Python", + lines_of_code=215, + severity_score=4.3, + complexity_score=6.1, + issues_found=[ + { + "title": "Missing retry logic", + "severity": "high", + "category": "reliability", + "line_number": 58, + "recommendation": "Add exponential backoff retry", + } + ], + recommendations=["Refactor long function"], + detailed_analysis="Processor heavily relies on synchronous calls.", + ) + ] + + metadata = { + "type": "module_analysis", + "chunk_metrics": {"total_issues": 1}, + "dependencies": {"depends_on_chunks": ["Auth", "Notifications"]}, + "timestamp": datetime.utcnow().isoformat(), + } + ai_response = "Detailed module analysis" + + payload = kg_ops.build_module_payload( + run_id=run_id, + repository_id=repository_id, + module_name=module_name, + chunk=chunk, + chunk_analysis=chunk_analysis, + file_analyses=file_analyses, + metadata=metadata, + ai_response=ai_response, + ) + + module_props = payload["module_props"] + files = payload["files"] + findings = payload["findings"] + dependencies = payload["dependencies"] + + assert module_props["name"] == module_name + assert module_props["total_files"] == len(file_analyses) + assert "analysis_payload" in module_props + assert files[0]["path"] == "services/payments/processor.py" + assert files[0]["props"]["language"] == "Python" + assert len(findings) == 1 + assert findings[0]["props"]["severity"] == "high" + assert dependencies[0]["target"] == "Auth" + assert dependencies[1]["target"] == "Notifications" + diff --git a/services/ai-analysis-service/test_multi_level_report.py b/services/ai-analysis-service/test_multi_level_report.py deleted file mode 100755 index 39b391e..0000000 --- a/services/ai-analysis-service/test_multi_level_report.py +++ /dev/null @@ -1,244 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script for multi-level report generation and context retrieval -""" - -import os -import sys -import asyncio -from pathlib import Path -from dotenv import load_dotenv - -# Add current directory to path -sys.path.insert(0, str(Path(__file__).parent)) - -load_dotenv() - -async def test_context_retrieval(): - """Test context retrieval functions.""" - print("\n" + "=" * 60) - print("Testing Context Retrieval Functions") - print("=" * 60) - - try: - from server import ( - retrieve_all_module_analyses, - retrieve_synthesis_analysis, - retrieve_cumulative_analysis_state, - retrieve_all_findings, - retrieve_all_metrics, - retrieve_comprehensive_report_context - ) - - print("āœ… All context retrieval functions imported") - - # Test with a dummy run_id - test_run_id = "test_run_123" - test_repository_id = "test_repo_123" - test_session_id = "test_session_123" - - print(f"\nTesting with run_id: {test_run_id}") - print(f"Repository ID: {test_repository_id}") - print(f"Session ID: {test_session_id}") - - # Test each function - print("\n1. Testing retrieve_all_module_analyses...") - modules = await retrieve_all_module_analyses(test_run_id, test_repository_id) - print(f" āœ“ Found {len(modules)} modules") - - print("\n2. Testing retrieve_synthesis_analysis...") - synthesis = await retrieve_synthesis_analysis(test_run_id, test_repository_id) - if synthesis: - print(f" āœ“ Found synthesis analysis") - else: - print(f" āš ļø No synthesis analysis found (expected for test)") - - print("\n3. Testing retrieve_cumulative_analysis_state...") - state = await retrieve_cumulative_analysis_state(test_run_id, test_repository_id, test_session_id) - if state: - print(f" āœ“ Found cumulative analysis state") - else: - print(f" āš ļø No cumulative analysis state found (expected for test)") - - print("\n4. Testing retrieve_all_findings...") - findings = await retrieve_all_findings(test_run_id) - print(f" āœ“ Found findings for {len(findings)} modules") - - print("\n5. Testing retrieve_all_metrics...") - metrics = await retrieve_all_metrics(test_run_id) - print(f" āœ“ Found metrics for {len(metrics)} modules") - - print("\n6. Testing retrieve_comprehensive_report_context...") - context = await retrieve_comprehensive_report_context( - run_id=test_run_id, - repository_id=test_repository_id, - session_id=test_session_id - ) - - print(f" āœ“ Context retrieved:") - print(f" - Modules: {context.get('total_modules', 0)}") - print(f" - Findings: {context.get('total_findings', 0)}") - print(f" - Has synthesis: {bool(context.get('synthesis_analysis'))}") - print(f" - Has analysis state: {bool(context.get('analysis_state'))}") - - print("\nāœ… All context retrieval tests passed!") - return True - - except Exception as e: - print(f"\nāŒ Context retrieval test failed: {e}") - import traceback - traceback.print_exc() - return False - -def test_pdf_method_exists(): - """Test that the new PDF method exists.""" - print("\n" + "=" * 60) - print("Testing PDF Report Method") - print("=" * 60) - - try: - # Import using the same method as server.py - import sys - import importlib.util - - spec = importlib.util.spec_from_file_location("ai_analyze", "ai-analyze.py") - ai_analyze_module = importlib.util.module_from_spec(spec) - sys.modules["ai_analyze"] = ai_analyze_module - spec.loader.exec_module(ai_analyze_module) - - from ai_analyze import EnhancedGitHubAnalyzer - - print("āœ… EnhancedGitHubAnalyzer imported successfully") - - # Check if new method exists - if hasattr(EnhancedGitHubAnalyzer, 'create_multi_level_pdf_report'): - print("āœ… create_multi_level_pdf_report method exists") - - # Check method signature - import inspect - sig = inspect.signature(EnhancedGitHubAnalyzer.create_multi_level_pdf_report) - params = list(sig.parameters.keys()) - print(f" Method parameters: {', '.join(params)}") - - if 'comprehensive_context' in params: - print(" āœ“ comprehensive_context parameter exists") - if 'output_path' in params: - print(" āœ“ output_path parameter exists") - if 'repository_id' in params: - print(" āœ“ repository_id parameter exists") - if 'run_id' in params: - print(" āœ“ run_id parameter exists") - - return True - else: - print("āŒ create_multi_level_pdf_report method not found") - return False - - except Exception as e: - print(f"āŒ PDF method test failed: {e}") - import traceback - traceback.print_exc() - return False - -def test_database_tables(): - """Test that database tables exist.""" - print("\n" + "=" * 60) - print("Testing Database Tables") - print("=" * 60) - - try: - import psycopg2 - from dotenv import load_dotenv - - load_dotenv() - - conn = psycopg2.connect( - host=os.getenv('POSTGRES_HOST', 'localhost'), - port=os.getenv('POSTGRES_PORT', '5432'), - database=os.getenv('POSTGRES_DB', 'dev_pipeline'), - user=os.getenv('POSTGRES_USER', 'pipeline_admin'), - password=os.getenv('POSTGRES_PASSWORD', 'secure_pipeline_2024') - ) - - cursor = conn.cursor() - - # Check each table - tables_to_check = ['findings', 'metrics', 'report_sections', 'analysis_runs'] - - for table_name in tables_to_check: - cursor.execute(f""" - SELECT COUNT(*) - FROM information_schema.tables - WHERE table_schema = 'public' - AND table_name = %s - """, (table_name,)) - - exists = cursor.fetchone()[0] > 0 - - if exists: - # Get row count - cursor.execute(f"SELECT COUNT(*) FROM {table_name}") - count = cursor.fetchone()[0] - print(f"āœ… Table '{table_name}' exists ({count} rows)") - else: - print(f"āŒ Table '{table_name}' does not exist") - return False - - cursor.close() - conn.close() - - print("\nāœ… All database tables verified!") - return True - - except Exception as e: - print(f"āŒ Database test failed: {e}") - import traceback - traceback.print_exc() - return False - -async def run_all_tests(): - """Run all tests.""" - print("\n" + "=" * 60) - print("MULTI-LEVEL REPORT IMPLEMENTATION TEST SUITE") - print("=" * 60) - - results = [] - - # Test 1: Database tables - results.append(("Database Tables", test_database_tables())) - - # Test 2: PDF method exists - results.append(("PDF Method", test_pdf_method_exists())) - - # Test 3: Context retrieval - results.append(("Context Retrieval", await test_context_retrieval())) - - # Summary - print("\n" + "=" * 60) - print("TEST SUMMARY") - print("=" * 60) - - passed = 0 - failed = 0 - - for test_name, result in results: - status = "āœ… PASSED" if result else "āŒ FAILED" - print(f"{test_name}: {status}") - if result: - passed += 1 - else: - failed += 1 - - print(f"\nTotal: {passed} passed, {failed} failed out of {len(results)} tests") - - if failed == 0: - print("\nāœ… All tests passed! Implementation is ready.") - return True - else: - print(f"\nāš ļø {failed} test(s) failed. Please review the errors above.") - return False - -if __name__ == "__main__": - success = asyncio.run(run_all_tests()) - sys.exit(0 if success else 1) - diff --git a/services/ai-analysis-service/test_progressive_context.py b/services/ai-analysis-service/test_progressive_context.py deleted file mode 100644 index 07a135c..0000000 --- a/services/ai-analysis-service/test_progressive_context.py +++ /dev/null @@ -1,309 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script for progressive context implementation. -Tests the logic without requiring actual API calls or database connections. -""" - -import sys -from pathlib import Path -from typing import Dict, List, Tuple - -# Add current directory to path -sys.path.insert(0, str(Path(__file__).parent)) - -# Import the functions we need to test -from server import ( - build_context_from_state, - update_state_with_findings, - create_intelligent_chunks, - build_intelligent_chunk_prompt -) - -# Mock FileAnalysis class -class MockFileAnalysis: - def __init__(self, path, severity_score, issues_found=None, complexity_score=5.0): - self.path = path - self.severity_score = severity_score - self.issues_found = issues_found or [] - self.complexity_score = complexity_score - self.language = "javascript" - self.lines_of_code = 100 - self.recommendations = [] - self.detailed_analysis = "Mock analysis" - -def test_build_context_from_state(): - """Test building context from analysis state.""" - print("=" * 60) - print("TEST 1: build_context_from_state()") - print("=" * 60) - - # Create analysis state with progressive data - analysis_state = { - 'modules_analyzed': ['project_overview', 'authentication'], - 'project_overview': 'Node.js e-commerce platform with Express backend and React frontend', - 'module_summaries': { - 'project_overview': 'Modern e-commerce platform with microservices architecture', - 'authentication': 'JWT-based authentication with rate limiting missing' - }, - 'architecture_patterns': ['MVC', 'Service Layer'], - 'critical_issues': [ - {'module': 'authentication', 'issue': 'Missing rate limiting on auth endpoints'} - ], - 'tech_stack': { - 'frontend': 'React', - 'backend': 'Node.js', - 'database': 'PostgreSQL' - }, - 'dependency_context': { - 'chunk_001': 'Project overview and setup', - 'chunk_002': 'Authentication module with JWT' - } - } - - # Test chunk (products module) - current_chunk = { - 'name': 'products', - 'id': 'chunk_003', - 'chunk_type': 'module', - 'context_dependencies': ['chunk_001', 'chunk_002'] - } - - context = build_context_from_state(analysis_state, current_chunk) - - print("\nāœ… Generated context:") - print(context) - print() - - # Verify context contains expected sections - assert "PROJECT OVERVIEW" in context, "Context should include project overview" - assert "PREVIOUSLY ANALYZED MODULES" in context, "Context should include module summaries" - assert "ARCHITECTURE PATTERNS" in context, "Context should include architecture patterns" - assert "CRITICAL ISSUES" in context, "Context should include critical issues" - assert "TECH STACK" in context, "Context should include tech stack" - assert "DEPENDENCY CONTEXT" in context, "Context should include dependency context" - - print("āœ… All context sections present!") - return True - -def test_update_state_with_findings(): - """Test updating analysis state with new findings.""" - print("\n" + "=" * 60) - print("TEST 2: update_state_with_findings()") - print("=" * 60) - - # Initial state - analysis_state = { - 'modules_analyzed': ['project_overview'], - 'module_summaries': { - 'project_overview': 'Node.js e-commerce platform' - }, - 'architecture_patterns': [], - 'critical_issues': [], - 'dependency_context': {} - } - - # New chunk analysis - chunk = { - 'name': 'authentication', - 'id': 'chunk_002', - 'chunk_type': 'module' - } - - chunk_analysis = { - 'module_overview': 'JWT-based authentication module with rate limiting missing', - 'module_architecture': 'Uses MVC pattern with Service Layer for business logic', - 'module_quality_score': 6.5 - } - - # Mock file analyses - file_analyses = [ - MockFileAnalysis('auth.controller.js', 7.0, ['No rate limiting']), - MockFileAnalysis('auth.service.js', 8.0), - MockFileAnalysis('auth.middleware.js', 4.0, ['Weak validation']) # Low quality - ] - - # Update state - updated_state = update_state_with_findings(analysis_state.copy(), chunk, chunk_analysis, file_analyses) - - print("\nāœ… Updated state:") - print(f" Modules analyzed: {updated_state.get('modules_analyzed', [])}") - print(f" Architecture patterns: {updated_state.get('architecture_patterns', [])}") - print(f" Critical issues: {len(updated_state.get('critical_issues', []))}") - print(f" Module summaries: {list(updated_state.get('module_summaries', {}).keys())}") - print() - - # Verify updates - assert 'authentication' in updated_state['modules_analyzed'], "Authentication should be in modules_analyzed" - assert 'MVC' in updated_state['architecture_patterns'], "MVC pattern should be detected" - assert 'Service Layer' in updated_state['architecture_patterns'], "Service Layer pattern should be detected" - assert len(updated_state['critical_issues']) > 0, "Critical issues should be added" - assert 'authentication' in updated_state['module_summaries'], "Module summary should be stored" - - print("āœ… State updated correctly!") - return True - -def test_progressive_context_flow(): - """Test the complete progressive context flow.""" - print("\n" + "=" * 60) - print("TEST 3: Progressive Context Flow (Simulated)") - print("=" * 60) - - # Simulate chunk processing flow - test_files = [ - ("README.md", "# Project\n\nNode.js e-commerce platform"), - ("package.json", '{"name": "ecommerce", "dependencies": {"express": "^4.0"}}'), - ("src/auth/auth.controller.js", "export class AuthController {}"), - ("src/auth/auth.service.js", "export class AuthService {}"), - ("src/products/product.controller.js", "export class ProductController {}"), - ] - - # Create chunks - chunks = create_intelligent_chunks(test_files) - - print(f"\nāœ… Created {len(chunks)} chunks:") - for chunk in chunks: - print(f" - {chunk['name']} ({chunk['chunk_type']}): {len(chunk['files'])} files") - - # Simulate progressive analysis - analysis_state = {} - - print("\nšŸ“Š Simulating progressive analysis:") - - for i, chunk in enumerate(chunks, 1): - chunk_name = chunk['name'] - print(f"\n Chunk {i}: {chunk_name}") - - # Build context (what would be used in prompt) - context = build_context_from_state(analysis_state, chunk) - if context: - print(f" šŸ“š Context available: {len(context)} chars") - else: - print(f" šŸ“š No context (first chunk)") - - # Simulate chunk analysis results - chunk_analysis = { - 'module_overview': f"Analysis of {chunk_name} module", - 'module_architecture': 'MVC pattern' if chunk_name != 'project_overview' else 'Node.js setup', - 'module_quality_score': 7.5 - } - - # Mock file analyses - file_analyses = [ - MockFileAnalysis(f"{chunk_name}_file{i}.js", 7.0 + i*0.1) - for i in range(len(chunk['files'])) - ] - - # Update state - analysis_state = update_state_with_findings(analysis_state.copy(), chunk, chunk_analysis, file_analyses) - - print(f" āœ… State updated: {len(analysis_state.get('modules_analyzed', []))} modules analyzed") - if analysis_state.get('architecture_patterns'): - print(f" šŸ“ Patterns: {', '.join(analysis_state.get('architecture_patterns', []))}") - - print("\nšŸ“Š Final Analysis State:") - print(f" Modules analyzed: {', '.join(analysis_state.get('modules_analyzed', []))}") - print(f" Architecture patterns: {', '.join(analysis_state.get('architecture_patterns', []))}") - print(f" Critical issues: {len(analysis_state.get('critical_issues', []))}") - print(f" Module summaries: {len(analysis_state.get('module_summaries', {}))}") - - # Verify final state - assert len(analysis_state.get('modules_analyzed', [])) == len(chunks), "All chunks should be analyzed" - assert len(analysis_state.get('architecture_patterns', [])) > 0, "Patterns should be detected" - - print("\nāœ… Progressive context flow working correctly!") - return True - -def test_prompt_includes_context(): - """Test that prompts include progressive context.""" - print("\n" + "=" * 60) - print("TEST 4: Prompt Includes Progressive Context") - print("=" * 60) - - # Create analysis state - analysis_state = { - 'modules_analyzed': ['project_overview', 'authentication'], - 'project_overview': 'Node.js platform', - 'module_summaries': { - 'authentication': 'JWT auth module' - }, - 'architecture_patterns': ['MVC'], - 'critical_issues': [ - {'module': 'authentication', 'issue': 'Missing rate limiting'} - ], - 'tech_stack': {'backend': 'Node.js'} - } - - # Test chunk - chunk = { - 'name': 'products', - 'chunk_type': 'module', - 'files': [('product.controller.js', 'export class ProductController {}')] - } - - # Build prompt - prompt = build_intelligent_chunk_prompt(chunk, analysis_state) - - print("\nāœ… Generated prompt (first 500 chars):") - print(prompt[:500]) - print("...") - print() - - # Verify prompt includes context - assert "CONTEXT FROM PREVIOUS ANALYSIS" in prompt, "Prompt should include context section" - assert "PROJECT OVERVIEW" in prompt, "Prompt should include project overview" - assert "PREVIOUSLY ANALYZED MODULES" in prompt, "Prompt should include module summaries" - assert "ARCHITECTURE PATTERNS" in prompt, "Prompt should include architecture patterns" - assert "CRITICAL ISSUES" in prompt, "Prompt should include critical issues" - - print("āœ… Prompt includes all context sections!") - - # Test without context (first chunk) - prompt_no_context = build_intelligent_chunk_prompt(chunk, None) - assert "CONTEXT FROM PREVIOUS ANALYSIS" not in prompt_no_context, "First chunk should not have context" - - print("āœ… Prompt correctly omits context for first chunk!") - return True - -def run_all_tests(): - """Run all tests.""" - print("\n" + "=" * 60) - print("PROGRESSIVE CONTEXT - COMPREHENSIVE TEST SUITE") - print("=" * 60) - - try: - # Test 1: Context building - test_build_context_from_state() - - # Test 2: State updates - test_update_state_with_findings() - - # Test 3: Complete flow - test_progressive_context_flow() - - # Test 4: Prompt generation - test_prompt_includes_context() - - print("\n" + "=" * 60) - print("āœ… ALL TESTS PASSED!") - print("=" * 60) - print("\nšŸ“Š Summary:") - print(" • Context building: āœ…") - print(" • State updates: āœ…") - print(" • Progressive flow: āœ…") - print(" • Prompt generation: āœ…") - print("\nšŸŽ‰ Progressive context implementation is working correctly!") - - return True - - except Exception as e: - print("\n" + "=" * 60) - print(f"āŒ TEST FAILED: {e}") - print("=" * 60) - import traceback - traceback.print_exc() - return False - -if __name__ == "__main__": - success = run_all_tests() - sys.exit(0 if success else 1) -