diff --git a/.gitignore b/.gitignore
index e2cb231..af1e875 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,6 +20,9 @@ logs/
*.log
/backend/error.log
/backend/combined.log
+combined.log
+error.log
+cron_sync.log
# Runtime data
pids
@@ -63,3 +66,16 @@ core.*
tmp/
temp/
frontend/firmadata/subvendor/bygma/bygma_esg_data.csv
+
+# Python cache
+__pycache__/
+*.pyc
+*.pyo
+*.pyd
+.Python
+
+# Test outputs
+test-results/
+playwright-report/
+*.pdf
+!docs/**/*.pdf
diff --git a/FEATURE_ROADMAP.md b/FEATURE_ROADMAP.md
deleted file mode 100644
index efaccda..0000000
--- a/FEATURE_ROADMAP.md
+++ /dev/null
@@ -1,2195 +0,0 @@
-# 🗺️ Feature Roadmap - Tilbudgivern
-
-**Oprettet**: 2025-10-19
-**Status**: Planning Phase
-
----
-
-## 📋 Feature Overview
-
-| # | Feature | Prioritet | Status | Estimeret Tid |
-|---|---------|-----------|--------|---------------|
-| 1 | Standardisering af opgavebeskrivelser | 🔴 Høj | 📋 Planlagt | 3-5 dage |
-| 2 | Geometri-beregning af tag | 🔴 Høj | 📋 Planlagt | 2-3 dage |
-| 3 | Smart pakker udvidelse | 🟡 Medium | 📋 Planlagt | 4-6 dage |
-| 4 | AI-baseret kundebeskrivelse | 🟢 Lav | 📋 Planlagt | 3-4 dage |
-| 5 | PDF-preview i final review | 🟡 Medium | 📋 Planlagt | 2-3 dage |
-| 6 | Web-scraper til montagevejledninger | 🟢 Lav | 📋 Planlagt | 5-7 dage |
-
-**Total Estimeret Tid**: 19-28 dage
-
----
-
-## 1️⃣ Standardisering af Opgavebeskrivelser
-
-### 📝 Beskrivelse
-Indfør key phrases (vinduer, døre, gulve, tage) så data kan segmenteres til erfaringspakker.
-
-### 🎯 Acceptance Criteria
-- [x] System genkender nøgleord i beskrivelser
-- [x] Beskrivelser mappe til standardiserede kategorier
-- [x] Kan eksportere statistik pr. kategori
-
-### 🔧 Teknisk Implementation
-
-#### Database Schema
-```sql
--- Ny tabel til standardiserede kategorier
-CREATE TABLE task_categories (
- id INT AUTO_INCREMENT PRIMARY KEY,
- category_name VARCHAR(100) NOT NULL,
- category_key VARCHAR(50) UNIQUE NOT NULL,
- description TEXT,
- icon VARCHAR(50),
- color_code VARCHAR(7),
- sort_order INT DEFAULT 0,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
-);
-
--- Key phrases til kategori matching
-CREATE TABLE category_keywords (
- id INT AUTO_INCREMENT PRIMARY KEY,
- category_id INT NOT NULL,
- keyword VARCHAR(100) NOT NULL,
- weight DECIMAL(3,2) DEFAULT 1.0,
- FOREIGN KEY (category_id) REFERENCES task_categories(id),
- INDEX idx_keyword (keyword)
-);
-
--- Link opgaver til kategorier
-ALTER TABLE project_tasks
-ADD COLUMN category_id INT,
-ADD COLUMN auto_categorized BOOLEAN DEFAULT FALSE,
-ADD COLUMN category_confidence DECIMAL(3,2),
-ADD FOREIGN KEY (category_id) REFERENCES task_categories(id);
-```
-
-#### Service: TaskCategorizationService
-```javascript
-// backend/src/services/taskCategorizationService.js
-
-class TaskCategorizationService {
-
- constructor() {
- this.categories = {
- vinduer: {
- keywords: ['vindu', 'vinduer', 'termoruder', 'karm', 'karme', 'ruder'],
- weight: { exact: 1.0, partial: 0.7 }
- },
- døre: {
- keywords: ['dør', 'døre', 'entre', 'entredør', 'indgang', 'udgang'],
- weight: { exact: 1.0, partial: 0.7 }
- },
- gulve: {
- keywords: ['gulv', 'gulve', 'trægulv', 'laminat', 'parket', 'vinyl', 'fliser'],
- weight: { exact: 1.0, partial: 0.7 }
- },
- tage: {
- keywords: ['tag', 'tage', 'tagplader', 'spær', 'lægter', 'undertag', 'tagrende'],
- weight: { exact: 1.0, partial: 0.7 }
- },
- vægge: {
- keywords: ['væg', 'vægge', 'facade', 'puds', 'maling', 'isolering'],
- weight: { exact: 1.0, partial: 0.7 }
- },
- el: {
- keywords: ['el', 'elektriker', 'stikkontakt', 'belysning', 'ledninger'],
- weight: { exact: 1.0, partial: 0.7 }
- },
- vvs: {
- keywords: ['vvs', 'rør', 'badeværelse', 'køkken', 'vandhane', 'afløb'],
- weight: { exact: 1.0, partial: 0.7 }
- }
- };
- }
-
- /**
- * Kategoriser en opgave baseret på beskrivelse
- * @param {string} description - Opgavebeskrivelse
- * @returns {Object} - { category, confidence, matchedKeywords }
- */
- categorizeTask(description) {
- const normalizedDesc = description.toLowerCase();
- const scores = {};
-
- for (const [category, config] of Object.entries(this.categories)) {
- let score = 0;
- const matchedKeywords = [];
-
- for (const keyword of config.keywords) {
- // Exact match
- if (normalizedDesc.includes(keyword)) {
- score += config.weight.exact;
- matchedKeywords.push({ keyword, type: 'exact' });
- }
- // Partial match (fuzzy)
- else if (this.fuzzyMatch(normalizedDesc, keyword)) {
- score += config.weight.partial;
- matchedKeywords.push({ keyword, type: 'partial' });
- }
- }
-
- if (score > 0) {
- scores[category] = {
- score,
- matchedKeywords,
- confidence: Math.min(score / config.keywords.length, 1.0)
- };
- }
- }
-
- // Find højeste score
- const bestMatch = Object.entries(scores)
- .sort(([, a], [, b]) => b.score - a.score)[0];
-
- if (!bestMatch) {
- return { category: 'andre', confidence: 0, matchedKeywords: [] };
- }
-
- const [category, data] = bestMatch;
- return {
- category,
- confidence: data.confidence,
- matchedKeywords: data.matchedKeywords
- };
- }
-
- /**
- * Fuzzy string matching
- */
- fuzzyMatch(text, keyword) {
- const threshold = 0.8;
- const distance = this.levenshteinDistance(text, keyword);
- const similarity = 1 - (distance / Math.max(text.length, keyword.length));
- return similarity >= threshold;
- }
-
- /**
- * Levenshtein distance for fuzzy matching
- */
- levenshteinDistance(str1, str2) {
- const matrix = [];
-
- for (let i = 0; i <= str2.length; i++) {
- matrix[i] = [i];
- }
-
- for (let j = 0; j <= str1.length; j++) {
- matrix[0][j] = j;
- }
-
- for (let i = 1; i <= str2.length; i++) {
- for (let j = 1; j <= str1.length; j++) {
- if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
- matrix[i][j] = matrix[i - 1][j - 1];
- } else {
- matrix[i][j] = Math.min(
- matrix[i - 1][j - 1] + 1,
- matrix[i][j - 1] + 1,
- matrix[i - 1][j] + 1
- );
- }
- }
- }
-
- return matrix[str2.length][str1.length];
- }
-
- /**
- * Batch kategoriser alle opgaver i et projekt
- */
- async categorizeProjectTasks(projectId) {
- const tasks = await db.query(
- 'SELECT id, task_description FROM project_tasks WHERE project_id = ?',
- [projectId]
- );
-
- const results = [];
-
- for (const task of tasks) {
- const categorization = this.categorizeTask(task.task_description);
-
- await db.query(`
- UPDATE project_tasks
- SET category_id = (SELECT id FROM task_categories WHERE category_key = ?),
- auto_categorized = TRUE,
- category_confidence = ?
- WHERE id = ?
- `, [categorization.category, categorization.confidence, task.id]);
-
- results.push({
- taskId: task.id,
- ...categorization
- });
- }
-
- return results;
- }
-
- /**
- * Eksporter statistik pr. kategori
- */
- async exportCategoryStatistics(filters = {}) {
- const { startDate, endDate, minConfidence = 0.7 } = filters;
-
- const query = `
- SELECT
- tc.category_name,
- tc.category_key,
- COUNT(pt.id) as task_count,
- SUM(pt.estimated_hours) as total_hours,
- AVG(pt.estimated_hours) as avg_hours_per_task,
- AVG(pt.category_confidence) as avg_confidence,
- SUM(pt.actual_cost) as total_cost
- FROM task_categories tc
- LEFT JOIN project_tasks pt ON pt.category_id = tc.id
- WHERE pt.category_confidence >= ?
- ${startDate ? 'AND pt.created_at >= ?' : ''}
- ${endDate ? 'AND pt.created_at <= ?' : ''}
- GROUP BY tc.id, tc.category_name, tc.category_key
- ORDER BY total_hours DESC
- `;
-
- const params = [minConfidence];
- if (startDate) params.push(startDate);
- if (endDate) params.push(endDate);
-
- return await db.query(query, params);
- }
-}
-
-module.exports = new TaskCategorizationService();
-```
-
-#### API Endpoints
-```javascript
-// Kategoriser en enkelt opgave
-app.post('/api/tasks/:taskId/categorize', async (req, res) => {
- const { taskId } = req.params;
- const task = await db.query('SELECT * FROM project_tasks WHERE id = ?', [taskId]);
-
- const result = taskCategorizationService.categorizeTask(task[0].task_description);
-
- await db.query(`
- UPDATE project_tasks
- SET category_id = (SELECT id FROM task_categories WHERE category_key = ?),
- auto_categorized = TRUE,
- category_confidence = ?
- WHERE id = ?
- `, [result.category, result.confidence, taskId]);
-
- res.json({ success: true, categorization: result });
-});
-
-// Kategoriser alle opgaver i et projekt
-app.post('/api/projects/:projectId/categorize-tasks', async (req, res) => {
- const { projectId } = req.params;
- const results = await taskCategorizationService.categorizeProjectTasks(projectId);
- res.json({ success: true, results });
-});
-
-// Eksporter kategori-statistik
-app.get('/api/statistics/categories', async (req, res) => {
- const { startDate, endDate, minConfidence } = req.query;
- const stats = await taskCategorizationService.exportCategoryStatistics({
- startDate, endDate, minConfidence: parseFloat(minConfidence)
- });
- res.json({ success: true, statistics: stats });
-});
-
-// Hent alle kategorier
-app.get('/api/categories', async (req, res) => {
- const categories = await db.query('SELECT * FROM task_categories ORDER BY sort_order');
- res.json({ success: true, categories });
-});
-```
-
-### 📊 Seed Data
-```sql
--- Indsæt standard kategorier
-INSERT INTO task_categories (category_name, category_key, icon, color_code, sort_order) VALUES
-('Vinduer', 'vinduer', '🪟', '#3b82f6', 1),
-('Døre', 'døre', '🚪', '#10b981', 2),
-('Gulve', 'gulve', '🏠', '#f59e0b', 3),
-('Tage', 'tage', '🏘️', '#ef4444', 4),
-('Vægge', 'vægge', '🧱', '#8b5cf6', 5),
-('El-arbejde', 'el', '⚡', '#eab308', 6),
-('VVS', 'vvs', '🚰', '#06b6d4', 7),
-('Andet', 'andre', '🔧', '#6b7280', 99);
-
--- Indsæt keywords
-INSERT INTO category_keywords (category_id, keyword, weight) VALUES
--- Vinduer
-(1, 'vindu', 1.0), (1, 'vinduer', 1.0), (1, 'termoruder', 1.0),
-(1, 'karm', 0.8), (1, 'karme', 0.8), (1, 'ruder', 0.7),
--- Døre
-(2, 'dør', 1.0), (2, 'døre', 1.0), (2, 'entre', 0.9),
-(2, 'entredør', 1.0), (2, 'indgang', 0.7), (2, 'udgang', 0.7),
--- Gulve
-(3, 'gulv', 1.0), (3, 'gulve', 1.0), (3, 'trægulv', 1.0),
-(3, 'laminat', 1.0), (3, 'parket', 1.0), (3, 'vinyl', 0.9),
--- Tage
-(4, 'tag', 1.0), (4, 'tage', 1.0), (4, 'tagplader', 1.0),
-(4, 'spær', 1.0), (4, 'lægter', 1.0), (4, 'undertag', 1.0),
--- VVS
-(7, 'vvs', 1.0), (7, 'rør', 0.8), (7, 'badeværelse', 0.7),
-(7, 'køkken', 0.6), (7, 'vandhane', 0.9), (7, 'afløb', 0.9);
-```
-
-### 🧪 Tests
-```javascript
-// test_task_categorization.js
-
-const service = require('./backend/src/services/taskCategorizationService');
-
-console.log('=== Testing Task Categorization ===\n');
-
-const testCases = [
- { desc: 'Montering af nye vinduer i stue', expected: 'vinduer' },
- { desc: 'Udskiftning af entredør med sikkerhedslås', expected: 'døre' },
- { desc: 'Lægning af trægulv i alle værelser', expected: 'gulve' },
- { desc: 'Renovering af tag med nye tagplader og spær', expected: 'tage' },
- { desc: 'Installation af stikkontakter og belysning', expected: 'el' },
- { desc: 'Montering af ny vandhane i køkken', expected: 'vvs' }
-];
-
-for (const test of testCases) {
- const result = service.categorizeTask(test.desc);
- const status = result.category === test.expected ? '✅' : '❌';
- console.log(`${status} "${test.desc}"`);
- console.log(` → ${result.category} (${(result.confidence * 100).toFixed(0)}% confidence)`);
- console.log(` → Keywords: ${result.matchedKeywords.map(k => k.keyword).join(', ')}\n`);
-}
-```
-
----
-
-## 2️⃣ Geometri-beregning af Tag
-
-### 📝 Beskrivelse
-Geometrimodulet skal håndtere 'stern til kip' korrekt og tillade ændring af grader og spærafstand.
-
-### 🎯 Acceptance Criteria
-- [x] Feltnavn ændres til korrekt begreb (ryghøjde)
-- [x] Gradfelt kan ændres efter beregning uden fejl
-- [x] Spærafstand kan tilpasses (0,6m / 1m / variabel)
-
-### 🔧 Teknisk Implementation
-
-#### Database Schema Update
-```sql
--- Opdater roof_geometry tabel
-ALTER TABLE roof_geometry
- -- Omdøb wall_height til ridge_to_stern_height (stern til kip)
- CHANGE COLUMN wall_height stern_to_ridge_height DECIMAL(10,2) COMMENT 'Højde fra stern til kip',
-
- -- Tilføj nye felter
- ADD COLUMN roof_angle_degrees DECIMAL(5,2) AFTER roof_pitch COMMENT 'Taghældning i grader',
- ADD COLUMN spaer_distance DECIMAL(5,2) DEFAULT 0.6 COMMENT 'Spærafstand i meter (0.6m, 1m, custom)',
- ADD COLUMN spaer_count INT COMMENT 'Antal spær beregnet',
- ADD COLUMN allow_angle_edit BOOLEAN DEFAULT TRUE COMMENT 'Tillad ændring af grader efter beregning',
- ADD COLUMN calculation_method ENUM('pitch', 'angle', 'manual') DEFAULT 'pitch' COMMENT 'Beregningsmetode';
-
--- Tilføj index for hurtigere queries
-CREATE INDEX idx_roof_angle ON roof_geometry(roof_angle_degrees);
-CREATE INDEX idx_spaer_distance ON roof_geometry(spaer_distance);
-```
-
-#### Frontend Component Update
-```jsx
-// frontend/src/components/GeometryInput.js
-
-function RoofGeometryInput({ projectId, onComplete }) {
- const [geometry, setGeometry] = useState({
- width: '',
- length: '',
- sternToRidgeHeight: '', // Renamed from wallHeight
- roofAngleDegrees: '', // New field
- roofPitch: '',
- spaerDistance: 0.6, // Default 0.6m
- calculationMethod: 'angle' // 'pitch', 'angle', or 'manual'
- });
-
- const [calculatedValues, setCalculatedValues] = useState(null);
- const [allowEdit, setAllowEdit] = useState(true);
-
- // Beregn taghældning fra grader eller omvendt
- const handleAngleChange = (angleDegrees) => {
- const angleRad = (angleDegrees * Math.PI) / 180;
- const pitch = Math.tan(angleRad);
-
- setGeometry(prev => ({
- ...prev,
- roofAngleDegrees: angleDegrees,
- roofPitch: pitch.toFixed(4),
- calculationMethod: 'angle'
- }));
-
- recalculateGeometry({ ...geometry, roofAngleDegrees: angleDegrees });
- };
-
- const handlePitchChange = (pitch) => {
- const angleDegrees = (Math.atan(pitch) * 180) / Math.PI;
-
- setGeometry(prev => ({
- ...prev,
- roofPitch: pitch,
- roofAngleDegrees: angleDegrees.toFixed(2),
- calculationMethod: 'pitch'
- }));
-
- recalculateGeometry({ ...geometry, roofPitch: pitch });
- };
-
- const recalculateGeometry = (updatedGeometry) => {
- if (!updatedGeometry.width || !updatedGeometry.length || !updatedGeometry.sternToRidgeHeight) {
- return;
- }
-
- const width = parseFloat(updatedGeometry.width);
- const length = parseFloat(updatedGeometry.length);
- const ridgeHeight = parseFloat(updatedGeometry.sternToRidgeHeight);
- const angle = parseFloat(updatedGeometry.roofAngleDegrees);
- const spaerDist = parseFloat(updatedGeometry.spaerDistance);
-
- // Beregn tagets geometri
- const halfWidth = width / 2;
- const roofSlope = Math.sqrt(Math.pow(halfWidth, 2) + Math.pow(ridgeHeight, 2));
- const roofArea = 2 * roofSlope * length;
-
- // Beregn antal spær
- const spaerCount = Math.ceil(length / spaerDist) + 1;
-
- // Beregn vindskede (diagonal)
- const vindskede = Math.sqrt(Math.pow(roofSlope, 2) + Math.pow(length, 2));
-
- setCalculatedValues({
- roofArea: roofArea.toFixed(2),
- roofSlope: roofSlope.toFixed(2),
- spaerCount,
- totalSpaerLength: (spaerCount * roofSlope * 2).toFixed(2),
- vindskede: vindskede.toFixed(2),
- actualSpaerDistance: (length / (spaerCount - 1)).toFixed(2)
- });
- };
-
- const handleSubmit = async () => {
- const payload = {
- roofWidth: geometry.width,
- roofLength: geometry.length,
- sternToRidgeHeight: geometry.sternToRidgeHeight,
- roofAngleDegrees: geometry.roofAngleDegrees,
- roofPitch: geometry.roofPitch,
- spaerDistance: geometry.spaerDistance,
- calculationMethod: geometry.calculationMethod,
- allowAngleEdit: allowEdit
- };
-
- const response = await fetch(`/api/customer-projects/${projectId}/geometry`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(payload)
- });
-
- const result = await response.json();
- if (result.success) {
- onComplete(result);
- }
- };
-
- return (
-
-
Tag Geometri
-
- {/* Basic dimensions */}
-
-
- {
- setGeometry({...geometry, width: e.target.value});
- recalculateGeometry({...geometry, width: e.target.value});
- }}
- />
-
-
-
-
- {
- setGeometry({...geometry, length: e.target.value});
- recalculateGeometry({...geometry, length: e.target.value});
- }}
- />
-
-
-
-
- {
- setGeometry({...geometry, sternToRidgeHeight: e.target.value});
- recalculateGeometry({...geometry, sternToRidgeHeight: e.target.value});
- }}
- />
-
-
- {/* Angle/Pitch input */}
-
-
- handleAngleChange(parseFloat(e.target.value))}
- disabled={!allowEdit && calculatedValues}
- />
- {!allowEdit && (
-
- )}
-
-
- {/* Spærafstand selector */}
-
-
-
-
-
- {
- setGeometry({...geometry, spaerDistance: e.target.value});
- recalculateGeometry({...geometry, spaerDistance: e.target.value});
- }}
- />
-
-
-
- {/* Calculated values */}
- {calculatedValues && (
-
-
Beregnede værdier:
-
-
📐 Tagareal: {calculatedValues.roofArea} m²
-
📏 Taghældning: {calculatedValues.roofSlope} m
-
🪚 Antal spær: {calculatedValues.spaerCount} stk
-
📏 Faktisk afstand: {calculatedValues.actualSpaerDistance} m
-
🔗 Total spærlængde: {calculatedValues.totalSpaerLength} m
-
📐 Vindskede: {calculatedValues.vindskede} lbm
-
-
- )}
-
-
-
- );
-}
-```
-
-#### Backend API Update
-```javascript
-// unified-server.js - Update geometry endpoint
-
-app.post('/api/customer-projects/:projectId/geometry', async (req, res) => {
- try {
- const { projectId } = req.params;
- const {
- roofWidth,
- roofLength,
- sternToRidgeHeight, // Renamed from wallHeight
- roofAngleDegrees, // New field
- roofPitch,
- roofType,
- complexity,
- spaerDistance = 0.6, // New field with default
- calculationMethod = 'angle',
- allowAngleEdit = true
- } = req.body;
-
- // Validation
- if (!roofWidth || !roofLength || !sternToRidgeHeight) {
- return res.status(400).json({
- success: false,
- error: 'Tag bredde, længde og højde stern til kip er påkrævet'
- });
- }
-
- // Calculate geometry
- const width = parseFloat(roofWidth);
- const length = parseFloat(roofLength);
- const ridgeHeight = parseFloat(sternToRidgeHeight);
- const angle = parseFloat(roofAngleDegrees);
- const spaerDist = parseFloat(spaerDistance);
-
- // Calculate roof area using Pythagoras
- const halfWidth = width / 2;
- const roofSlope = Math.sqrt(Math.pow(halfWidth, 2) + Math.pow(ridgeHeight, 2));
- const totalArea = 2 * roofSlope * length;
-
- // Calculate spær count
- const spaerCount = Math.ceil(length / spaerDist) + 1;
- const totalSpaerLength = spaerCount * roofSlope * 2;
-
- // Calculate vindskede
- const vindskede = Math.sqrt(Math.pow(roofSlope, 2) + Math.pow(length, 2));
-
- // Save to database
- await databaseService.query(`
- INSERT INTO roof_geometry (
- project_id, width_main, length_main, total_area,
- stern_to_ridge_height, roof_angle_degrees, roof_pitch,
- roof_type, complexity_factor, roof_height, vindskede_lbm,
- spaer_distance, spaer_count, allow_angle_edit, calculation_method,
- created_at, updated_at
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
- ON DUPLICATE KEY UPDATE
- width_main = VALUES(width_main),
- length_main = VALUES(length_main),
- total_area = VALUES(total_area),
- stern_to_ridge_height = VALUES(stern_to_ridge_height),
- roof_angle_degrees = VALUES(roof_angle_degrees),
- roof_pitch = VALUES(roof_pitch),
- spaer_distance = VALUES(spaer_distance),
- spaer_count = VALUES(spaer_count),
- allow_angle_edit = VALUES(allow_angle_edit),
- calculation_method = VALUES(calculation_method),
- updated_at = NOW()
- `, [
- projectId, width, length, totalArea,
- ridgeHeight, angle, roofPitch,
- roofType, complexity, roofSlope, vindskede,
- spaerDist, spaerCount, allowAngleEdit, calculationMethod
- ]);
-
- res.json({
- success: true,
- geometry: {
- area: totalArea.toFixed(2),
- roofSlope: roofSlope.toFixed(2),
- spaerCount,
- totalSpaerLength: totalSpaerLength.toFixed(2),
- vindskede: vindskede.toFixed(2),
- actualSpaerDistance: (length / (spaerCount - 1)).toFixed(2)
- }
- });
-
- } catch (error) {
- console.error('Geometry calculation error:', error);
- res.status(500).json({ success: false, error: error.message });
- }
-});
-```
-
----
-
-## 3️⃣ Smart Pakker Udvidelse
-
-### 📝 Beskrivelse
-Smart pakker skal indeholde materialer, tidsforbrug og opgaver i korrekt rækkefølge.
-
-### 🎯 Acceptance Criteria
-- [x] Pakker gemmes med defineret rækkefølge af opgaver
-- [x] Materialer og tid kobles automatisk til geometridata
-- [x] Final review viser samlet tids- og materialeberegning
-
-### 🔧 Teknisk Implementation
-
-#### Database Schema
-```sql
--- Tilføj task ordering til smart packages
-ALTER TABLE package_tasks
- ADD COLUMN task_order INT DEFAULT 0 COMMENT 'Rækkefølge i pakken',
- ADD COLUMN task_phase ENUM('forberedelse', 'hovedarbejde', 'finishing') DEFAULT 'hovedarbejde',
- ADD COLUMN depends_on_task_id INT COMMENT 'Opgave der skal være færdig først',
- ADD FOREIGN KEY (depends_on_task_id) REFERENCES package_tasks(id);
-
--- Tilføj geometri-kobling til materialer
-ALTER TABLE package_materials
- ADD COLUMN geometry_multiplier VARCHAR(50) COMMENT 'Formel: area, length, width, spaer_count',
- ADD COLUMN base_quantity DECIMAL(10,2) COMMENT 'Basisantal før multiplikation',
- ADD COLUMN waste_factor DECIMAL(5,2) DEFAULT 1.1 COMMENT 'Spild-faktor (1.1 = 10% spild)';
-
-CREATE INDEX idx_task_order ON package_tasks(smart_package_id, task_order);
-```
-
-#### Service Implementation
-```javascript
-// backend/src/services/smartPackageService.js
-
-class SmartPackageService {
-
- /**
- * Hent smart pakke med opgaver i korrekt rækkefølge
- */
- async getPackageWithOrderedTasks(packageId) {
- const packageData = await db.query(
- 'SELECT * FROM smart_packages WHERE id = ?',
- [packageId]
- );
-
- const tasks = await db.query(`
- SELECT
- pt.*,
- pt.task_order,
- pt.task_phase,
- pt.depends_on_task_id,
- dt.task_name as depends_on_task_name
- FROM package_tasks pt
- LEFT JOIN package_tasks dt ON pt.depends_on_task_id = dt.id
- WHERE pt.smart_package_id = ?
- ORDER BY pt.task_phase, pt.task_order ASC
- `, [packageId]);
-
- const materials = await db.query(`
- SELECT
- pm.*,
- pm.geometry_multiplier,
- pm.base_quantity,
- pm.waste_factor
- FROM package_materials pm
- WHERE pm.smart_package_id = ?
- ORDER BY pm.material_category, pm.material_name
- `, [packageId]);
-
- return {
- package: packageData[0],
- tasks,
- materials
- };
- }
-
- /**
- * Beregn materialer baseret på geometri
- */
- calculateMaterialsForGeometry(materials, geometry) {
- return materials.map(material => {
- let quantity = material.base_quantity || 0;
-
- // Apply geometry multiplier
- switch (material.geometry_multiplier) {
- case 'area':
- quantity = geometry.total_area * material.base_quantity;
- break;
- case 'length':
- quantity = geometry.length_main * material.base_quantity;
- break;
- case 'width':
- quantity = geometry.width_main * material.base_quantity;
- break;
- case 'spaer_count':
- quantity = geometry.spaer_count * material.base_quantity;
- break;
- case 'perimeter':
- quantity = (2 * geometry.width_main + 2 * geometry.length_main) * material.base_quantity;
- break;
- }
-
- // Apply waste factor
- const finalQuantity = quantity * (material.waste_factor || 1.1);
-
- return {
- ...material,
- calculated_quantity: Math.ceil(finalQuantity),
- base_calculation: quantity,
- waste_amount: finalQuantity - quantity
- };
- });
- }
-
- /**
- * Generer final review data
- */
- async generateFinalReview(projectId) {
- // Get project data
- const project = await db.query(
- 'SELECT * FROM customer_projects WHERE id = ?',
- [projectId]
- );
-
- // Get geometry
- const geometry = await db.query(
- 'SELECT * FROM roof_geometry WHERE project_id = ?',
- [projectId]
- );
-
- // Get selected packages
- const packages = await db.query(`
- SELECT sp.*, cpp.quantity
- FROM smart_packages sp
- JOIN customer_project_packages cpp ON sp.id = cpp.smart_package_id
- WHERE cpp.project_id = ?
- `, [projectId]);
-
- const packageDetails = [];
- let totalHours = 0;
- let totalMaterialCost = 0;
-
- for (const pkg of packages) {
- const details = await this.getPackageWithOrderedTasks(pkg.id);
- const materials = this.calculateMaterialsForGeometry(
- details.materials,
- geometry[0]
- );
-
- // Calculate costs
- const packageHours = details.tasks.reduce((sum, task) =>
- sum + (task.estimated_hours || 0), 0
- );
- const packageMaterialCost = materials.reduce((sum, mat) =>
- sum + (mat.calculated_quantity * mat.unit_price || 0), 0
- );
-
- totalHours += packageHours;
- totalMaterialCost += packageMaterialCost;
-
- packageDetails.push({
- package: pkg,
- tasks: details.tasks,
- materials,
- summary: {
- hours: packageHours,
- laborCost: packageHours * 1626, // Default hourly rate
- materialCost: packageMaterialCost,
- totalCost: (packageHours * 1626) + packageMaterialCost
- }
- });
- }
-
- return {
- project: project[0],
- geometry: geometry[0],
- packages: packageDetails,
- totals: {
- hours: totalHours,
- laborCost: totalHours * 1626,
- materialCost: totalMaterialCost,
- totalCost: (totalHours * 1626) + totalMaterialCost,
- vat: ((totalHours * 1626) + totalMaterialCost) * 0.25
- }
- };
- }
-}
-
-module.exports = new SmartPackageService();
-```
-
----
-
-## 4️⃣ AI-baseret Kundebeskrivelse
-
-### 📝 Beskrivelse
-Generér en kundevenlig tekst ud fra montagevejledningen og indsæt i tilbudsbeskrivelsen.
-
-### 🎯 Acceptance Criteria
-- [x] Beskrivelse er letforståelig og kundevenlig
-- [x] Teknisk montagevejledning gemmes separat i opgavefeltet
-- [x] Begge dele overføres korrekt til Ordrestyring
-
-### 🔧 Teknisk Implementation
-
-#### Database Schema
-```sql
--- Tilføj felter til beskrivelser
-ALTER TABLE package_tasks
- ADD COLUMN technical_description TEXT COMMENT 'Teknisk montagevejledning',
- ADD COLUMN customer_friendly_description TEXT COMMENT 'Kundevenlig beskrivelse',
- ADD COLUMN ai_generated BOOLEAN DEFAULT FALSE,
- ADD COLUMN ai_generation_timestamp TIMESTAMP NULL;
-
-ALTER TABLE customer_projects
- ADD COLUMN customer_description TEXT COMMENT 'Samlet kundevenlig beskrivelse',
- ADD COLUMN technical_notes TEXT COMMENT 'Tekniske noter til montører';
-```
-
-#### AI Service
-```javascript
-// backend/src/services/aiDescriptionService.js
-
-const OpenAI = require('openai');
-
-class AIDescriptionService {
-
- constructor() {
- this.openai = new OpenAI({
- apiKey: process.env.OPENAI_API_KEY
- });
- }
-
- /**
- * Generér kundevenlig beskrivelse fra teknisk tekst
- */
- async generateCustomerFriendlyDescription(technicalDescription, context = {}) {
- const prompt = `
-Du er en ekspert i at kommunikere byggeprojekter til kunder på en letforståelig måde.
-
-TEKNISK BESKRIVELSE:
-${technicalDescription}
-
-KONTEKST:
-- Projekt type: ${context.projectType || 'Byggeprojekt'}
-- Område: ${context.area || 'Ikke specificeret'}
-- Kunde niveau: ${context.customerLevel || 'Standard'}
-
-OPGAVE:
-Omskriv den tekniske beskrivelse til en kundevenlig tekst der:
-1. Er let at forstå for en person uden byggefaglig baggrund
-2. Fokuserer på fordele og resultatet for kunden
-3. Undgår teknisk jargon (eller forklarer det simpelt)
-4. Er positiv og professionel i tonen
-5. Er max 3-4 sætninger
-
-KUNDEVENLIG BESKRIVELSE:
- `.trim();
-
- try {
- const completion = await this.openai.chat.completions.create({
- model: "gpt-4",
- messages: [
- {
- role: "system",
- content: "Du er en professionel kommunikationsekspert inden for byggeri. Du omskriver tekniske beskrivelser til kundevenlige tekster."
- },
- {
- role: "user",
- content: prompt
- }
- ],
- temperature: 0.7,
- max_tokens: 300
- });
-
- return completion.choices[0].message.content.trim();
- } catch (error) {
- console.error('AI generation error:', error);
- throw new Error('Kunne ikke generere kundevenlig beskrivelse');
- }
- }
-
- /**
- * Generér beskrivelse for alle opgaver i en pakke
- */
- async generateDescriptionsForPackage(packageId) {
- const tasks = await db.query(
- 'SELECT * FROM package_tasks WHERE smart_package_id = ?',
- [packageId]
- );
-
- const results = [];
-
- for (const task of tasks) {
- if (!task.technical_description) {
- console.log(`⚠️ No technical description for task ${task.id}, skipping`);
- continue;
- }
-
- const customerDescription = await this.generateCustomerFriendlyDescription(
- task.technical_description,
- {
- projectType: 'Tag udskiftning',
- area: 'Tag'
- }
- );
-
- await db.query(`
- UPDATE package_tasks
- SET customer_friendly_description = ?,
- ai_generated = TRUE,
- ai_generation_timestamp = NOW()
- WHERE id = ?
- `, [customerDescription, task.id]);
-
- results.push({
- taskId: task.id,
- taskName: task.task_name,
- technical: task.technical_description,
- customerFriendly: customerDescription
- });
- }
-
- return results;
- }
-
- /**
- * Generér samlet projektbeskrivelse
- */
- async generateProjectDescription(projectId) {
- const tasks = await db.query(`
- SELECT
- pt.customer_friendly_description,
- pt.task_name,
- pt.task_order,
- sp.package_name
- FROM package_tasks pt
- JOIN customer_project_packages cpp ON pt.smart_package_id = cpp.smart_package_id
- JOIN smart_packages sp ON sp.id = cpp.smart_package_id
- WHERE cpp.project_id = ?
- ORDER BY pt.task_order
- `, [projectId]);
-
- // Gruppér efter pakke
- const packageGroups = {};
- for (const task of tasks) {
- if (!packageGroups[task.package_name]) {
- packageGroups[task.package_name] = [];
- }
- packageGroups[task.package_name].push(task);
- }
-
- // Byg samlet beskrivelse
- let description = '## Hvad indgår i projektet?\n\n';
-
- for (const [packageName, packageTasks] of Object.entries(packageGroups)) {
- description += `### ${packageName}\n`;
- for (const task of packageTasks) {
- if (task.customer_friendly_description) {
- description += `- ${task.customer_friendly_description}\n`;
- }
- }
- description += '\n';
- }
-
- await db.query(
- 'UPDATE customer_projects SET customer_description = ? WHERE id = ?',
- [description, projectId]
- );
-
- return description;
- }
-
- /**
- * Sync til Ordrestyring med begge beskrivelser
- */
- async syncToOrdrestyring(projectId) {
- const project = await db.query(
- 'SELECT * FROM customer_projects WHERE id = ?',
- [projectId]
- );
-
- const tasks = await db.query(`
- SELECT
- pt.task_name,
- pt.technical_description,
- pt.customer_friendly_description,
- pt.estimated_hours
- FROM package_tasks pt
- JOIN customer_project_packages cpp ON pt.smart_package_id = cpp.smart_package_id
- WHERE cpp.project_id = ?
- `, [projectId]);
-
- // Format til Ordrestyring
- const ordrestyringPayload = {
- project_name: project[0].project_name,
- customer_description: project[0].customer_description,
- tasks: tasks.map(task => ({
- name: task.task_name,
- description: task.customer_friendly_description, // Til kunden
- technical_notes: task.technical_description, // Til montører
- estimated_hours: task.estimated_hours
- }))
- };
-
- // Send til Ordrestyring API
- const response = await fetch(`${process.env.ORDRESTYRING_API_URL}/projects`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${process.env.ORDRESTYRING_API_TOKEN}`
- },
- body: JSON.stringify(ordrestyringPayload)
- });
-
- return await response.json();
- }
-}
-
-module.exports = new AIDescriptionService();
-```
-
-#### API Endpoints
-```javascript
-// Generér kundevenlig beskrivelse for en opgave
-app.post('/api/tasks/:taskId/generate-description', async (req, res) => {
- const { taskId } = req.params;
- const task = await db.query('SELECT * FROM package_tasks WHERE id = ?', [taskId]);
-
- const description = await aiDescriptionService.generateCustomerFriendlyDescription(
- task[0].technical_description
- );
-
- await db.query(
- 'UPDATE package_tasks SET customer_friendly_description = ?, ai_generated = TRUE WHERE id = ?',
- [description, taskId]
- );
-
- res.json({ success: true, description });
-});
-
-// Generér beskrivelser for hele pakken
-app.post('/api/packages/:packageId/generate-descriptions', async (req, res) => {
- const { packageId } = req.params;
- const results = await aiDescriptionService.generateDescriptionsForPackage(packageId);
- res.json({ success: true, results });
-});
-
-// Generér samlet projektbeskrivelse
-app.post('/api/projects/:projectId/generate-description', async (req, res) => {
- const { projectId } = req.params;
- const description = await aiDescriptionService.generateProjectDescription(projectId);
- res.json({ success: true, description });
-});
-
-// Sync til Ordrestyring
-app.post('/api/projects/:projectId/sync-to-ordrestyring', async (req, res) => {
- const { projectId } = req.params;
- const result = await aiDescriptionService.syncToOrdrestyring(projectId);
- res.json({ success: true, result });
-});
-```
-
-#### Frontend Component
-```jsx
-// frontend/src/components/AIDescriptionGenerator.js
-
-function AIDescriptionGenerator({ taskId, technicalDescription, onGenerated }) {
- const [loading, setLoading] = useState(false);
- const [customerDescription, setCustomerDescription] = useState('');
-
- const generateDescription = async () => {
- setLoading(true);
- try {
- const response = await fetch(`/api/tasks/${taskId}/generate-description`, {
- method: 'POST'
- });
- const result = await response.json();
-
- if (result.success) {
- setCustomerDescription(result.description);
- onGenerated(result.description);
- }
- } catch (error) {
- console.error('Error generating description:', error);
- } finally {
- setLoading(false);
- }
- };
-
- return (
-
-
-
Teknisk beskrivelse (til montører):
-
{technicalDescription}
-
-
-
-
- {customerDescription && (
-
-
Kundevenlig beskrivelse:
-
{customerDescription}
-
- )}
-
- );
-}
-```
-
----
-
-## 5️⃣ PDF-preview i Final Review
-
-### 📝 Beskrivelse
-Systemet skal tilbyde en PDF-forhåndsvisning af tilbuddet med logo og formatering.
-
-### 🎯 Acceptance Criteria
-- [x] Knap i final review åbner en PDF-preview
-- [x] PDF indeholder alle relevante detaljer og firmaets logo
-- [x] PDF'en kan gemmes eller sendes videre
-
-### 🔧 Teknisk Implementation
-
-#### Install Dependencies
-```bash
-npm install puppeteer pdfkit --save
-```
-
-#### PDF Generation Service
-```javascript
-// backend/src/services/pdfGenerationService.js
-
-const puppeteer = require('puppeteer');
-const fs = require('fs').promises;
-const path = require('path');
-
-class PDFGenerationService {
-
- /**
- * Generér PDF tilbud
- */
- async generateQuotePDF(projectId) {
- // Get all data
- const project = await db.query(
- 'SELECT * FROM customer_projects WHERE id = ?',
- [projectId]
- );
-
- const geometry = await db.query(
- 'SELECT * FROM roof_geometry WHERE project_id = ?',
- [projectId]
- );
-
- const packages = await db.query(`
- SELECT
- sp.*,
- cpp.quantity,
- cpp.custom_price
- FROM smart_packages sp
- JOIN customer_project_packages cpp ON sp.id = cpp.smart_package_id
- WHERE cpp.project_id = ?
- `, [projectId]);
-
- const tasks = await db.query(`
- SELECT
- pt.task_name,
- pt.customer_friendly_description,
- pt.estimated_hours,
- sp.package_name
- FROM package_tasks pt
- JOIN customer_project_packages cpp ON pt.smart_package_id = cpp.smart_package_id
- JOIN smart_packages sp ON sp.id = cpp.smart_package_id
- WHERE cpp.project_id = ?
- ORDER BY pt.task_order
- `, [projectId]);
-
- const materials = await db.query(`
- SELECT
- pm.material_name,
- pm.calculated_quantity,
- pm.unit_price,
- pm.unit,
- sp.package_name
- FROM package_materials pm
- JOIN customer_project_packages cpp ON pm.smart_package_id = cpp.smart_package_id
- JOIN smart_packages sp ON sp.id = cpp.smart_package_id
- WHERE cpp.project_id = ?
- `, [projectId]);
-
- // Calculate totals
- const totalHours = tasks.reduce((sum, t) => sum + t.estimated_hours, 0);
- const laborCost = totalHours * 1626;
- const materialCost = materials.reduce((sum, m) =>
- sum + (m.calculated_quantity * m.unit_price), 0
- );
- const subtotal = laborCost + materialCost;
- const vat = subtotal * 0.25;
- const total = subtotal + vat;
-
- // Generate HTML
- const html = this.generateQuoteHTML({
- project: project[0],
- geometry: geometry[0],
- packages,
- tasks,
- materials,
- totals: {
- hours: totalHours,
- laborCost,
- materialCost,
- subtotal,
- vat,
- total
- }
- });
-
- // Generate PDF from HTML
- const browser = await puppeteer.launch({
- headless: true,
- args: ['--no-sandbox', '--disable-setuid-sandbox']
- });
-
- const page = await browser.newPage();
- await page.setContent(html, { waitUntil: 'networkidle0' });
-
- const pdfBuffer = await page.pdf({
- format: 'A4',
- margin: {
- top: '20mm',
- right: '15mm',
- bottom: '20mm',
- left: '15mm'
- },
- printBackground: true
- });
-
- await browser.close();
-
- // Save PDF
- const filename = `tilbud_${projectId}_${Date.now()}.pdf`;
- const filepath = path.join(__dirname, '../../public/quotes', filename);
- await fs.writeFile(filepath, pdfBuffer);
-
- // Save reference in database
- await db.query(
- 'UPDATE customer_projects SET quote_pdf_path = ? WHERE id = ?',
- [filename, projectId]
- );
-
- return {
- filename,
- filepath,
- url: `/quotes/${filename}`
- };
- }
-
- /**
- * Generér HTML til PDF
- */
- generateQuoteHTML(data) {
- const { project, geometry, packages, tasks, materials, totals } = data;
- const today = new Date().toLocaleDateString('da-DK');
-
- return `
-
-
-
-
- Tilbud - ${project.project_name}
-
-
-
-
-
- Tilbud
- Dato: ${today}
-
-
-
Kundeoplysninger
-
- Navn:
- ${project.customer_name}
-
- Adresse:
- ${project.customer_address || 'Ikke angivet'}
-
- Email:
- ${project.customer_email || 'Ikke angivet'}
-
- Telefon:
- ${project.customer_phone || 'Ikke angivet'}
-
-
-
-
-
Projektbeskrivelse
-
- Projekt:
- ${project.project_name}
-
- Type:
- Tag udskiftning
-
- ${geometry ? `
- Tagareal:
- ${geometry.total_area} m²
-
- Dimensioner:
- ${geometry.width_main}m × ${geometry.length_main}m
- ` : ''}
-
-
- ${project.customer_description ? `
-
-
${project.customer_description.replace(/\n/g, '
')}
-
- ` : ''}
-
-
-
-
Arbejdsopgaver
-
-
-
- | Opgave |
- Beskrivelse |
- Timer |
-
-
-
- ${tasks.map(task => `
-
- | ${task.task_name} |
- ${task.customer_friendly_description || '-'} |
- ${task.estimated_hours}h |
-
- `).join('')}
-
-
-
-
-
-
Materialer
-
-
-
- | Materiale |
- Antal |
- Enhed |
- Pris/enhed |
- Total |
-
-
-
- ${materials.map(mat => `
-
- | ${mat.material_name} |
- ${mat.calculated_quantity} |
- ${mat.unit} |
- ${mat.unit_price.toFixed(2)} kr. |
- ${(mat.calculated_quantity * mat.unit_price).toFixed(2)} kr. |
-
- `).join('')}
-
-
-
-
-
-
- | Arbejdsløn (${totals.hours}h á 1.626 kr.): |
- ${totals.laborCost.toFixed(2)} kr. |
-
-
- | Materialer: |
- ${totals.materialCost.toFixed(2)} kr. |
-
-
- | Subtotal: |
- ${totals.subtotal.toFixed(2)} kr. |
-
-
- | Moms (25%): |
- ${totals.vat.toFixed(2)} kr. |
-
-
- | TOTAL: |
- ${totals.total.toFixed(2)} kr. |
-
-
-
-
-
Betalingsbetingelser
-
- Betaling: Netto 30 dage
- Tilbuddet er gyldigt i 30 dage fra dato.
- Priserne er ekskl. moms, medmindre andet er angivet.
-
-
-
-
-
-
- `;
- }
-}
-
-module.exports = new PDFGenerationService();
-```
-
-#### API Endpoint
-```javascript
-// Generér PDF tilbud
-app.post('/api/projects/:projectId/generate-pdf', async (req, res) => {
- try {
- const { projectId } = req.params;
- const result = await pdfGenerationService.generateQuotePDF(projectId);
- res.json({
- success: true,
- pdfUrl: result.url,
- filename: result.filename
- });
- } catch (error) {
- console.error('PDF generation error:', error);
- res.status(500).json({ success: false, error: error.message });
- }
-});
-
-// Download PDF
-app.get('/api/projects/:projectId/download-pdf', async (req, res) => {
- const { projectId } = req.params;
- const project = await db.query(
- 'SELECT quote_pdf_path FROM customer_projects WHERE id = ?',
- [projectId]
- );
-
- if (!project[0] || !project[0].quote_pdf_path) {
- return res.status(404).json({ error: 'PDF ikke fundet' });
- }
-
- const filepath = path.join(__dirname, 'public/quotes', project[0].quote_pdf_path);
- res.download(filepath);
-});
-```
-
-#### Frontend Component
-```jsx
-// frontend/src/components/PDFPreview.js
-
-function PDFPreview({ projectId }) {
- const [pdfUrl, setPdfUrl] = useState(null);
- const [loading, setLoading] = useState(false);
-
- const generatePDF = async () => {
- setLoading(true);
- try {
- const response = await fetch(`/api/projects/${projectId}/generate-pdf`, {
- method: 'POST'
- });
- const result = await response.json();
-
- if (result.success) {
- setPdfUrl(result.pdfUrl);
- }
- } catch (error) {
- console.error('Error generating PDF:', error);
- } finally {
- setLoading(false);
- }
- };
-
- return (
-
-
-
- {pdfUrl && (
-
- )}
-
- );
-}
-```
-
----
-
-## 6️⃣ Web-scraper til Montagevejledninger
-
-### 📝 Beskrivelse
-Systemet skal kunne logge ind på Bygma og hente montagevejledninger (PDF) automatisk.
-
-### 🎯 Acceptance Criteria
-- [x] Web-scraper logger ind med gyldige brugeroplysninger
-- [x] Henter PDF-links til montagevejledninger
-- [x] Gemmer PDF'er lokalt til brug i smartpakker
-
-### 🔧 Teknisk Implementation
-
-#### Install Dependencies
-```bash
-npm install puppeteer axios cheerio --save
-```
-
-#### Web Scraper Service
-```javascript
-// backend/src/services/bygmaScraperService.js
-
-const puppeteer = require('puppeteer');
-const axios = require('axios');
-const fs = require('fs').promises;
-const path = require('path');
-
-class BygmaScraperService {
-
- constructor() {
- this.baseUrl = 'https://www.bygma.dk';
- this.loginUrl = 'https://www.bygma.dk/login';
- this.credentials = {
- username: process.env.BYGMA_USERNAME,
- password: process.env.BYGMA_PASSWORD
- };
- }
-
- /**
- * Login til Bygma
- */
- async login(page) {
- console.log('🔐 Logger ind på Bygma...');
-
- await page.goto(this.loginUrl, { waitUntil: 'networkidle2' });
-
- // Udfyld login formular
- await page.type('#username', this.credentials.username);
- await page.type('#password', this.credentials.password);
-
- // Klik login knap
- await Promise.all([
- page.waitForNavigation({ waitUntil: 'networkidle2' }),
- page.click('button[type="submit"]')
- ]);
-
- console.log('✅ Login succesfuldt');
- }
-
- /**
- * Søg efter produkt
- */
- async searchProduct(page, productNumber) {
- console.log(`🔍 Søger efter produkt: ${productNumber}`);
-
- await page.goto(`${this.baseUrl}/search?q=${productNumber}`, {
- waitUntil: 'networkidle2'
- });
-
- // Klik på første resultat
- const firstProduct = await page.$('.product-item a');
- if (!firstProduct) {
- throw new Error(`Produkt ${productNumber} ikke fundet`);
- }
-
- await Promise.all([
- page.waitForNavigation({ waitUntil: 'networkidle2' }),
- firstProduct.click()
- ]);
- }
-
- /**
- * Hent montagevejledning links
- */
- async getInstallationGuideLinks(page) {
- console.log('📄 Henter montagevejledninger...');
-
- // Find download sektion
- const pdfLinks = await page.evaluate(() => {
- const links = [];
- const downloadSection = document.querySelector('.downloads') ||
- document.querySelector('.documents');
-
- if (downloadSection) {
- const pdfElements = downloadSection.querySelectorAll('a[href$=".pdf"]');
- pdfElements.forEach(el => {
- links.push({
- title: el.textContent.trim(),
- url: el.href
- });
- });
- }
-
- return links;
- });
-
- console.log(`✅ Fundet ${pdfLinks.length} PDF'er`);
- return pdfLinks;
- }
-
- /**
- * Download PDF
- */
- async downloadPDF(url, filename) {
- console.log(`⬇️ Downloader: ${filename}`);
-
- const response = await axios({
- method: 'GET',
- url: url,
- responseType: 'arraybuffer'
- });
-
- const filepath = path.join(__dirname, '../../public/manuals', filename);
- await fs.writeFile(filepath, response.data);
-
- console.log(`✅ Gemt: ${filepath}`);
- return filepath;
- }
-
- /**
- * Hent alle montagevejledninger for et produkt
- */
- async fetchInstallationGuides(productNumber) {
- const browser = await puppeteer.launch({
- headless: true,
- args: ['--no-sandbox', '--disable-setuid-sandbox']
- });
-
- try {
- const page = await browser.newPage();
-
- // Login
- await this.login(page);
-
- // Søg produkt
- await this.searchProduct(page, productNumber);
-
- // Hent PDF links
- const pdfLinks = await this.getInstallationGuideLinks(page);
-
- // Download alle PDF'er
- const downloadedFiles = [];
- for (const link of pdfLinks) {
- const filename = `${productNumber}_${link.title.replace(/[^a-z0-9]/gi, '_')}.pdf`;
- const filepath = await this.downloadPDF(link.url, filename);
- downloadedFiles.push({
- title: link.title,
- filename,
- filepath,
- productNumber
- });
- }
-
- // Gem reference i database
- for (const file of downloadedFiles) {
- await db.query(`
- INSERT INTO installation_manuals (
- product_number,
- title,
- filename,
- filepath,
- source,
- downloaded_at
- ) VALUES (?, ?, ?, ?, 'bygma', NOW())
- `, [file.productNumber, file.title, file.filename, file.filepath]);
- }
-
- return downloadedFiles;
-
- } finally {
- await browser.close();
- }
- }
-
- /**
- * Batch hent vejledninger for flere produkter
- */
- async fetchBulkInstallationGuides(productNumbers) {
- const results = {
- success: [],
- failed: []
- };
-
- for (const productNumber of productNumbers) {
- try {
- const files = await this.fetchInstallationGuides(productNumber);
- results.success.push({
- productNumber,
- files
- });
- } catch (error) {
- console.error(`❌ Fejl ved ${productNumber}:`, error.message);
- results.failed.push({
- productNumber,
- error: error.message
- });
- }
-
- // Vent mellem requests for at undgå rate limiting
- await new Promise(resolve => setTimeout(resolve, 2000));
- }
-
- return results;
- }
-
- /**
- * Hent vejledning fra cache hvis den findes
- */
- async getManualFromCache(productNumber) {
- const manuals = await db.query(
- 'SELECT * FROM installation_manuals WHERE product_number = ? ORDER BY downloaded_at DESC',
- [productNumber]
- );
-
- return manuals;
- }
-}
-
-module.exports = new BygmaScraperService();
-```
-
-#### Database Schema
-```sql
--- Tabel til montagevejledninger
-CREATE TABLE installation_manuals (
- id INT AUTO_INCREMENT PRIMARY KEY,
- product_number VARCHAR(50) NOT NULL,
- title VARCHAR(255) NOT NULL,
- filename VARCHAR(255) NOT NULL,
- filepath VARCHAR(500) NOT NULL,
- source ENUM('bygma', 'manual_upload', 'other') DEFAULT 'bygma',
- file_size INT COMMENT 'Filstørrelse i bytes',
- downloaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- last_accessed TIMESTAMP NULL,
- INDEX idx_product_number (product_number),
- INDEX idx_source (source)
-);
-
--- Link vejledninger til materialer
-ALTER TABLE package_materials
- ADD COLUMN installation_manual_id INT,
- ADD FOREIGN KEY (installation_manual_id) REFERENCES installation_manuals(id);
-```
-
-#### API Endpoints
-```javascript
-// Hent montagevejledninger for et produkt
-app.post('/api/manuals/fetch/:productNumber', async (req, res) => {
- try {
- const { productNumber } = req.params;
-
- // Check cache først
- const cached = await bygmaScraperService.getManualFromCache(productNumber);
- if (cached.length > 0) {
- return res.json({
- success: true,
- source: 'cache',
- manuals: cached
- });
- }
-
- // Hent fra Bygma
- const files = await bygmaScraperService.fetchInstallationGuides(productNumber);
- res.json({
- success: true,
- source: 'bygma',
- manuals: files
- });
- } catch (error) {
- console.error('Manual fetch error:', error);
- res.status(500).json({ success: false, error: error.message });
- }
-});
-
-// Batch hent vejledninger
-app.post('/api/manuals/fetch-bulk', async (req, res) => {
- const { productNumbers } = req.body;
-
- if (!Array.isArray(productNumbers)) {
- return res.status(400).json({ error: 'productNumbers skal være et array' });
- }
-
- const results = await bygmaScraperService.fetchBulkInstallationGuides(productNumbers);
- res.json({ success: true, results });
-});
-
-// Hent vejledning fra cache
-app.get('/api/manuals/:productNumber', async (req, res) => {
- const { productNumber } = req.params;
- const manuals = await bygmaScraperService.getManualFromCache(productNumber);
- res.json({ success: true, manuals });
-});
-
-// Download manual fil
-app.get('/api/manuals/:id/download', async (req, res) => {
- const { id } = req.params;
- const manual = await db.query(
- 'SELECT * FROM installation_manuals WHERE id = ?',
- [id]
- );
-
- if (!manual[0]) {
- return res.status(404).json({ error: 'Manual ikke fundet' });
- }
-
- // Update last accessed
- await db.query(
- 'UPDATE installation_manuals SET last_accessed = NOW() WHERE id = ?',
- [id]
- );
-
- res.download(manual[0].filepath);
-});
-```
-
-#### Frontend Component
-```jsx
-// frontend/src/components/ManualFetcher.js
-
-function ManualFetcher({ productNumber, onFetched }) {
- const [loading, setLoading] = useState(false);
- const [manuals, setManuals] = useState([]);
-
- const fetchManuals = async () => {
- setLoading(true);
- try {
- const response = await fetch(`/api/manuals/fetch/${productNumber}`, {
- method: 'POST'
- });
- const result = await response.json();
-
- if (result.success) {
- setManuals(result.manuals);
- onFetched(result.manuals);
- }
- } catch (error) {
- console.error('Error fetching manuals:', error);
- } finally {
- setLoading(false);
- }
- };
-
- return (
-
-
-
- {manuals.length > 0 && (
-
-
Montagevejledninger:
-
- {manuals.map(manual => (
- -
-
- 📄 {manual.title}
-
-
- {new Date(manual.downloaded_at).toLocaleDateString('da-DK')}
-
-
- ))}
-
-
- )}
-
- );
-}
-```
-
----
-
-## 📅 Implementation Timeline
-
-### Uge 1-2: Foundation
-- [ ] Database schema updates (alle features)
-- [ ] Basic service implementations
-- [ ] API endpoint struktur
-
-### Uge 3: Feature 1 & 2
-- [ ] Task categorization service
-- [ ] Geometry calculation updates
-- [ ] Testing og validering
-
-### Uge 4: Feature 3 & 4
-- [ ] Smart pakker udvidelse
-- [ ] AI description service
-- [ ] OpenAI integration
-
-### Uge 5: Feature 5 & 6
-- [ ] PDF generation service
-- [ ] Bygma web scraper
-- [ ] Testing og optimering
-
-### Uge 6: Integration & Testing
-- [ ] End-to-end testing
-- [ ] Performance optimization
-- [ ] Documentation
-- [ ] Deployment
-
----
-
-## 🧪 Testing Strategy
-
-### Unit Tests
-```javascript
-// tests/services/taskCategorization.test.js
-// tests/services/geometry.test.js
-// tests/services/aiDescription.test.js
-// tests/services/pdfGeneration.test.js
-// tests/services/bygmaScraper.test.js
-```
-
-### Integration Tests
-```javascript
-// tests/integration/smartPackages.test.js
-// tests/integration/finalReview.test.js
-```
-
-### E2E Tests
-```javascript
-// tests/e2e/createProject.test.js
-// tests/e2e/generateQuote.test.js
-```
-
----
-
-## 📝 Environment Variables Required
-
-```env
-# OpenAI for AI descriptions
-OPENAI_API_KEY=sk-...
-
-# Bygma credentials
-BYGMA_USERNAME=your_username
-BYGMA_PASSWORD=your_password
-
-# Ordrestyring API
-ORDRESTYRING_API_URL=https://api.ordrestyring.dk
-ORDRESTYRING_API_TOKEN=your_token
-
-# File paths
-MANUALS_DIR=/path/to/manuals
-QUOTES_PDF_DIR=/path/to/quotes
-```
-
----
-
-**Status**: 📋 Komplet Roadmap
-**Klar til**: Implementation
-**Estimeret Tid**: 19-28 dage (4-6 uger)
diff --git a/Test_Tilbud.pdf b/Test_Tilbud.pdf
deleted file mode 100644
index da316dd..0000000
--- a/Test_Tilbud.pdf
+++ /dev/null
@@ -1,226 +0,0 @@
-%PDF-1.4
-%“Œ‹ž ReportLab Generated PDF document http://www.reportlab.com
-1 0 obj
-<<
-/F1 2 0 R /F2 5 0 R /F3 9 0 R /F4 10 0 R /F5 12 0 R
->>
-endobj
-2 0 obj
-<<
-/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
->>
-endobj
-3 0 obj
-<<
-/BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter [ /ASCII85Decode /FlateDecode ] /Height 127 /Length 32056 /SMask 4 0 R
- /Subtype /Image /Type /XObject /Width 143
->>
-stream
-Gb"-6#C@.d[P%J2n$Pk(OpHpJOb5AH@"SoUMu]OSP/nLQ6rrZEF3ZdijmMSC]36&tR<.?B1O$k%q51,]Zs$a8]2j=2g2!nJD#X>)n(5<]s*jqDp$^N4e5t[;pJR5Fj3n!m7[*eHO74)..=NmV6BR`H7HA2LT1cpUO!BjI2A?IQX@Pr^_'q-FOI`3rbEnspRrk(5F8YW$nJC@fKCMH4)R@SC"$4RI4-.n"rpn9Bq?34!$Gm9ArL>90<+A=5dU*0lQ:WBs&\lq7f.P<2\ps&$WT/=0qKkS"2S:5\f7T,NT8X04^ci,"nIs#?X#6MWduq2&Gg%J6gl?FR5PmCOE9^)^JRL2l:DhY(l7f6t!9rO]d%i_aps2kTlqgTdIXT6IAQS^R5H_83:_DgFKHs1!"1r.hbea>[;2:f3Sb;a(L`j'P0>^F$9$p;.bOQ6fCM3UK$Ogt00C]r)P3B#?8dIXh%-mh\atHuS92bo>5ck.U)plc7CJ7k[d\@L#l3SZr^J[I-+i=jQqE]c1#HD-YQ!@864ee_M7H?lOGS1qZ@Am=_YKt2DZ:mIif.+VgFs37YFCfH?!9"e7dp\QV5+Sk>)1(F)gD]KhEb2.7kHTHt&7,a0VRl>J+n*A]>II"/U5?taq3qE%^gFKaT8XUhKN*?'=qjIpa:cL5c71)[f4=%EK[:^61a*#^77F5tSUP7E/WQW02Fp6Kb]bi_bBleAXI74-C.Y0JmpIb6_:2Y:b\n\W=jLs_18-*omM[orYnD^q0R<_AN#r,_Ad^P#b66W9qXqBj!>^h",JOX.fGo@:-C.om#!HT-_hCg,Z$mXOtR!Xu0ohq5A]a-NF8$ZBZa\t+jjTcf3oMnG:o+-A*R8/CHO`.fNp,lG64-tTB7K0C)k.i)>\/9%=s:'D7>\R$r8]R7#+CGuqCq?nnCCL>VIp[,#-f<+=T?dmc7[^WTF]5q9X`qKC+m0E?$*PHVFSVe#kjhJ!2.Ob)td-\$0=cd]$:D#UiqWu?&+85(_o)8!)gHgc5RrEJNg9ILO`opFtRTcgeJMFhP(*^rEo'`]A#RX*2'p-FshPa7X(?'?\R@la%Gk<aIIOsVJ>-:R9(*Zmr9=-ZsVp]HM"SH^sW"dI9$#&*,obq\iVX`Q%skA="%VZ'41XT?N4PJ/R'K#esR]nO_%DJ_?'+j94tgpXQbOkqu/`?T0Ig/.ct991J.!iEc.&BA'RV.@#N1h@Peum8F1BLX]\/-r&;'&[Vsh9p,aEs/J7guO82#"=j0@WWY?*J+CS:rY!-slHk6%)26'[@fs'W*a^5*eQc'2`g/P9(Z=\\:e04FGT[.@-BJoBSMmGJ3!o=1[mX-Y+Eto%B7%RcjZ[VP$rPTkaN/'gS#p(4NkI=^'Q:Y`)8,^j[\=AF(6pXf%Y_lsYOG%p2iZGcj\@oYXLE\L@_%hd^qH-E]bbFTXAOSYo)o$^%>0]!58-q>/AJ,+C1:,2s>M]0@Nl_tR*;=efl^jg<%%_."S*&VC&bq1^)t6i(=7'pW.e^K?)9q+o1@B[KNY9553Fa/)pDE7f$X.aUB>'bOSF)N^%a//!4(nBQl_J)&ni$!PoVk,I'9["<1K'ceh$2lo3MGMq!@_h7,I]m9E1k]b&l_+H^`_c*@,@K-98As%7%YW3m/fZ&!/D#2(G#?c@]7IU(17t:HnC,3T9d1\6L0i!SU8hh=XlZGis-+D'`8]RMjTJ2R9Hk4ii"hp##(%U$o-5DS16:&BDkCNAJ3c&uOeS6t2bsn2I&e+W[O9ES>:ZE)H&BXJLfkfAOe=#rt;Eb3QO;[Bc/5]@e;8cYK&.8`/k2&nj(cls?-Nr702Qc:WL_7FT"k;FPdGq=nm9I>?9D>$/^*!+<[[Zrqa=5:FqEKU81i!4D#r"Xfj5g[3IQ@'!QHh@P]3E$jnD&kB,"`oJi`2;W&3-8bLI35u:5:&*#aELY8S1MSG_n\e<\3;Ui?1*h2AqGXSO'G:lZH6\*c;@7.cBuQ6NMQX)@6Fk7)F5*?6XX9+G^p_ciAWt4V>YaZ)iF=Np/_t]B+rahJiH)aesedXL[a'^a"Zs:bErq-O"Z=i!n^@p)]2RQ=TN0MIFeQ/p@Bg\GKZ<0e(m8>n^WdqO`as588-l)>>5MVP2.c^,Gdci#@6$uk>P[VrbNWZ1fh<85[?d!VR/8D#G#4d4L.X^I!3KrnXVfYTDl)gK"aTCjc'>+b5p/8stj/&\33='JsP^?E^t=/ZTotAd[B?*fo#l(:-jKXasYAoi0/^3YCph.2VT[Ko&baSk1="SU.u,5L:ui/VQ7\,&jqnWE@6=1\"T"kB0mc::hCB&qQWh+`Yan+ccUgZT.9TS$JG-:1q@KcT%[rne9VTf(_m`s@\J,;[qC[=)Vl+]RDIu5,>R9)]4MoBKbRs=hqg!9Rk#[=mp^>%>MC%gp&GR3PE;C]$KWJ]/B"glRO;iP?F`9\:09Np:k74qT@UY;(aS[;8gtO6\=@^MN1jPc?97FRfLDg2?Di?lRS3o6.6b4DbrG3jJBsER-RE5Q#h8oOE<=LV$o"/YY,Y!RXCNMi2!PhlAmo1X:2F_YkA`c+.%(Km;3I?dAaM>7$"(-?IFZk@kFfMJ[5t4^2fm]bAK$i[&L!(++G_O2&0j)rL9sM[1oFDItJ:F1:D+ocN/tmLeC2.UcuQo98iBa,PXn]_q.6V^Ks9DJgrXGtmfpsfKfdk!h*!e)eeNWaKM&t-%R<'nJ4p&1Lt-^JiB9Ik1p%(mW*236L`bp;MeP#,Nf'+FY)JS.R.egUTh\P*>r)<1,(g%SOB/fO@j4RBfI_\17)5ZT?NZ7LTS\YPb5a"k7_[bR"WOLe[mWq"EY6XWE6d+gGQd1;ZSr%aihk1f+sL$]bfA#;;T*$_.U(M'Q+o71U&Y==RtkGhEFmeAt^*7A-Y%!JCQjj.!jR?t_7P_jb6&Jn4i@M6Z'25f8J1)b*Pog\5F8/Ui&W#'Mh^XoI:/1f&l_%&_KO7jJ7MXoIh)mO_b3'lH>q)L/E"[01CL6@hF%r#c<&GSIFm;F:`K,Ds\Pi=d>QYdGeEbIRd;MmL2A\1L*H)U0H9(e1G44;StT/=WOPE1*h/+FK/7^Z;VeP4;)V>*LK(K7`^-:^&AObd@JAnF_)_&Vn9@-g=T^Go;q6D5d5]"a:$'Do/"sbg3k+Fp*,+(pAO=f^PQc21Qg$'/gMSsSlo.3\aB5-t2Rf[hbidEBCa,?AP`"-d3L>VY:"i$gB&7%,Usnip=/L3Mel_!QCG=@P2Q%3CY#YUM#9tpW=>[m7S?a,?8F#!86MNI(N'R[?]#A28`WjOm3B:O9e[*'Wg*Fn_MdaFc21HWD8KY9TpBK>ZS%6oXX=EJ+;%`ZC5h7%"RMmY1`2CTPtBKUK,]3V1cQ_e`1g=WF:6[7YB']:ob9@+5GJs\[H;0G8F$S>-/Je66Y"=noOg\AuUt1D"*V)&k^KB4R2?>$Ob>5\`2#M$'&Yg@W2D02/&I=LAK4!Ii"C7_QfXd(16`8C3\t`o&hVV-L];Wn5Dg)@hQkWK>:KirQ@Kg\XC/'r:%-IFGYPL/[lg=JMJ)d!/UIR!&A*XC5=X/)R%m%lj"8WH*it3/uQ*\A`-)O;=).r8Z.3\;s3GO3mXIT#ibRFfF2BbFKF-CdkUIbWCX!oeIh^0R73MN$NDp"="a2`=PF&lfWf0qs/&W>94!A;'[.K>'=7b4$RS*%Lr5[\:(!$gq@f?ao+LNuM+#R(2W.@2*oIVINUPe,'trWjoF$Bf[Ko*5oeOm".NJnO]P`edjgI0T8Rn3Aub6>]*t,sO37_En+XAC/q?aIciD@R*1=nc$O,i2-tt+Mnu5GR;Pu/oRFMJ!PtDXm2A-=s2%+5`>.?bD$-_Z9`9LI8F.a$[BA#)KqW=EqXOd"q#u\03*Zl?dlX0kj-?7K"``F9MZ@aeX6\iWthh&%:qC&BcToa/_sWPCe4ajeD%7PifV],i.abZac.TfiJm83V+:+:&*SYLc)W+O^#k'F-">cdbSr8Y%eN&L`$gAn`;Q=Ke(-2;i>U$1_=Gj27oc!"IoLi8[e;"cfWK;>G$/Mrb79;EhcknK]Y;_C/fZ?oZ(24MrddN_t>)F$$HeT=\&P5`fr5=D?E&72-<&o`_\)&IEf$E`GF53`s'QTKPpj^_I<,7#hhG.e1t0,>hWi^f56tL3Na?>[&]OhiU`);,JH:m8."iEQcpHh.r/QQoCk:;NSe(Vo3fOmV:/54XNM"C*SPD9)KY36qs3RNPVGJ-?V:Pb>$W(:0KLXnjKIb^Q^1sDk*,A*eFMd1EQHI0BO@FXd\11#`6j;q-#Q_R6>YYQggp5@22=AIQT]9Xj0pBj90b=neBpRl]e=0/ifdkmK)!P2<##9P%.'rX%,Pu\s/4N%Y?U#S,O(&mHNkfEG4p(-DFaT+B]7%ZLT(RnQBWA`Hic%X*2NtqKI.D"8)/:$_QSL5qCk)Qn1LbT[-7SX8rTQ@;10k1I%dE>"$SNf'CO@SOp#-=tPke+3%"amaQM7>U3[t>&BR5-s3P%j]:o&3h]i3n:4k"K4%V7'Qi$qLpNB.lkUQeC]uEp^M1#WR@4L-?T%.,d?%^4kJE\YikgJB%;ie7@bSQ`Du6]HIFXXdcJ)82b#^+FJ("ghrF7>g,o.,dTX6$E=8JMR`RNDZa_(lWX='dscr="4skkDPRGaJ;`8NuNWbL&nI\-'FZ^K=@R80>Fu";2]).3(*Y'W$F0nE%tMuf*mJeBkNB?iR45>XEKqb5mdFlqkb9@oI;LM2g/mat[Y82+#+9H\?McQbn;l-O<_q$97?^c(IVE]kVKL^3hl+r[eOueDTuPZN_Xa%%Nf(BKC7eGHUr-f"NPXd=SZZ.u9$C4%UCjQ[N%b[VP/Ur="p-9Dn-Xj\$3t@SqJp#U!*fP#B5KZAc!,_[GFeWTI4"4o;r@Z^ra%faW%\O8:>Zrt7p*s,rV$Yg!?"l,+On-NnUGbj?F*6YpL*5PYu%5g?S"0,^59ZLYdk$",h^BWI'Dm$6jl"OAle%Qi[!arpP0/OTLqUO`cc"qb2Zi7:`icQp_EWGdSV;iqPrXj=`LWp"hgtLe2gD!\$Jg91a=H"U%He#?F>jc,b!M]ee!+f$VSop(Blh-)O`L+H[(:/^m1+0B2OU*/+TYQJm(5+"[7Goe+Bun'Sg!poWg)VZt:V;b?J4c%/-NF&'?bC:<2EGnr?\0SWscHsiq8BBiK@g9!Q"RjhsgA2;(O'1hU'SdW$FBE7W9J#Q!PO3Qg,fMuPB6-MgSk3c!57&GZJnE_[7Fgl!]0t:]#,ic4Wj3Q;cJ#sI`5A:m59i.Fo3rI[Zd4Wtm5]5s)CTRZ%?SUgT:b--RPunIIfMJd%Zfqn8Q6P6/eJV!LGRSfuEm5p[)gi\$ctBhTM_$B7=Fd9^*&`=p_uar5^n3k,TuYHXF3-3IKrp^;gS;!)cVCZ$GQ#$;'qMFO>?6A`>TJMA*nEP>R[&K](ifad>i<`6.+oA1S_i\cD):I6^;F4sf4_"5_+6hFs6Y`Ve\IqpaW^T(^b-n_ID.BUXZ"eJ>n`=!lM"5[C>cHebiV%]]d'YrYOLaeW=V?jPFjKE?%9S5.-T%mV=XnS$Qa1uNtIG^)IgnCu0aE58*l,jU_i,'*2A9ah+0".BGPOp,Y._tDbXH7-Kn.UL8p!?b*_]h,,[LVlHL/7(&t_^Ee[R\h?:Lui[6l*;$s3Dnd6aXA\l!bYB/l#[G9E:\Z`YW8Pn9gM;-X$uQm*Yd'okh(sJ`b3+7c(D\.i.Y*XELkV,aLHYs#;\-(!LDECg+!\;W"s2Y/R'i]W$N5p=m=K?+5EO`cSAs4*?/_Qq?`hR:82tf4c"N[$n'k&crmVqF%K$2]'*r[m^*foG"]B-j%(Jk(_TfRPfqu#'u[WeD:%a;`&a.dbC$.NLRWm&P(e4t%HTI'@N@B:LO>=`&NO)*[nF`YTob>P!LYV(db9E.>V;l"Y7d4f8OS%hYc"OM2(_./nS@7=cp^3JWk`<]aD]F'Ut0:Vek-F8BBb>u@PcLRce#,T;
9/u;jD/JU&]?fCfQ_!%5kZ="f,3>eOUURc.$,jWXsJ$iR(OCY]&Q&7m[L[:_l5"$+DdK=Vn"8CMUKa+nR<3N=?UO;u5J4BAF^Tm?Cn,kLH>3)$d-YpX:!]Fo1`<\ZX7>$#=2:OAT#HcZnE`?B@r_$K.NQcHPBbM7YGU,..o[.m&"HfPj^=!meus^Hoe@9?^$`N3:$X@MV-Gp@cGUm\n7('M/9UIPj`hRbprBBmLd`7X;;d++i+U!"7,Y?\_fjhgN;$PO3*Y2::k\1oaaI<9LWq"jd4aQU%ZdIFKHOg44>'4\qDf>f4UN_N3,gq5Lfldkju3ti.mhgYL]hQo]_VOotuo(_Y58,&W=I/*'MICt(/jRKQ(:S0atnES]UQdaEeVOrIu1U!\5cHNqLL#^_\R29dK5'un\onsV^?Tio/r;"5JI.Q%_Qd\`;9L"!rnd/'ZH2_XB`Nscl?Hg'.p1iaSlW;PRAmW,rrSmLNQZ$6FI.lRAo[=$u*oEN;E9lJRWLHgr',^?9PtOK2cC@%Er:nT&ID:!P^uhbIS,,(Hn))L&[lg.(m?-Z:,UU;-gh(L#S7^KbDdk#Cr!aSC.bVY9o59bq?dW$=j6rIai;uSb.![fauG_9,rb*]KB:n(cDD_jq+rP#,gnW@,%>eC"tESeW_6SW>-pnSi1#[e'UCXk\83UP8/X518gso#p^u)";;lq]E'W#,m[E=i(78HU"%r/BYoI#7;'?`8-"-r`pfAQT5ZI.Z.gdGVt7at9C-X&hA+XUMd%?YQ9G\!g'@B7[a&oX?G%#QlUh2]'=C3btuW(W&V5;es.(m4$=e@%Uf\2'NS+d*B?GZs9)K;WBZ@LF2YBM'8=-V#lGj?P$>g'4<#A-L:Lk;$X('[4-UWM1T#DDW2+=\1eQ7-'`*i(J;=WDu2S+pLNp!I=06VGgH)[g^Rg!(gpp.=m3uWU#rifZ1[M1)]?og"k1@V=[c$:eb`c*'n>h_0`?4jG=k!>a>i";'XJ)aZJ*bfqemU8\1ffk)Q4!$a^nS.Fr0t]Z_eM<.c6`/==YIEmUm(Lb#cQRVFWhFd\3$>e8L"4h<2L,sQ/"XaVH-Ws(E#6-Kst$p<2G[=AZF*n1iA*7R\t/\[pcQ)Lj5UWEp9Bs$o=Z6!As[)jR/P`0Jm;b9"%C4@uO1@#e\5o#VbMlm7g&4(S7)Al_36BB2cfaO4*$E:;_8cUT.U>)t>ZHTfY>1"-c@nILXhdXJ#'i?],'0OA=f9@cpSnI0f,R"B)2.0B0$.ug]"Qp[CPlk(:E_@Gu_nuab6P,X"?n:&X&[\9d#%2__M/gF62la+WGY?R(@$\T%TnL1e?YN49-Rpu>WBaW8fU&$&MTh&;q%cq.<;V3W+K46Z>c_i#o=>;&?K46SaU`*d#tHRS0#eJsM>8Qdp@-0PT1InD'&C`"5*6a8.PX`ll?u--6(\[F-FB-HkaTrstLe/\TEqAP",*[eOMpDOE6sP]GjW^9Bc/jO^1+6`:8d0hrPPT^l'mElJDOf:Jgh+0GA81UT.:aub(GrVIl>Bm6Qoo"#53@67X'&.7n",?]4_RgOJhac#8^P7(M&kC,=M+1'iaBplVqoe/a.Dr.*LC;"f93c`5dB2H[\Gd+D$f;YEq$BiP.&3f9(pX"/+E,jlH3U@FklN2adS4k9DnBs.:ep-6?r8^C#j3N%ZMdsL.#mVhX`-g-\lW1>+9"_oS3W#juoSW28`J'`7+Ll^^Wqjf@L5?Rb;l)m?ih/$-@%:!K4V>sC,hk>FVm]Z9-F+_&/Yd[lET4?g%u-YcS$!_h:7O%ts(7n6,LuD2R@CdEPD72c`b/f3SDF$X>^u.Mq;6?H:&SOK4!E^U]#!4"OlOH2!ZF7<tr'2A^piL.oF@cds9],`0Z`odPu\.@sZb*-S"Q9g(R[12[>hKY[_Y(r/"_0OLn@aq!Xh\3j>mOpfK=)=s#m=V4q3&]3gNn?JVPA23,f%%3aR>EfF6oj@e@?l-RXKR857X^\,E6Hu;F4W`A3Ye5]=uheSX(UVH?E2i]9;P1HHV%klD5_dJ]9Ms!im@7*D@AWnaGS1820qB*1nD@Z6aVGc^0j`A\02t2,AZZrr^:Fl)^04c*QW;cW.c>UTpX&8K*fcM^s@la8--4W#\PhT8['kVh.4$oTV\9^C.*o:5"(qst&^V24m"&!:MbRKo0WMiV/-(EHS;E67K2O;m>[HEr17SW(6"!a@q..QkG.VhT'V>X\27OoP#`WT8IJoU[nj:V\/!U!iR*Fl"/?DtdnJl2XXZDL^Cq\QbAADu7'\IXmYX$;D[0.aAq$,*lkTO<"MoGE`4K?WLO[TUr;OBbE"78IInJb/CPW=X6CgL>GIet\Y=58[-[)po*8C$&p^qe'BALC-4^dV[nUran(Zros[-_i^sKM5t3fLb)pjF!8Gt+.Dks,1Mm"I.#FS]_tjHALb5A8i9g.]QQFVOt0k8ipb2d#2`38\M#`HXAYjoj2kEKnK)!\A!sa\rgciPjrY)"j"<@XGrPM.ZOjR**YAHWe(rp=S_M:5(-^UR3U`N>)5"S@WQfj2TD$VYpT5rD5#V-h><(;OGW`Y6I+UK(7k,l1ZObq7lSDlCiPVpdER@4niHQl,o^QP8c)4%Z@TJ<(r[^Cm-Z`kmMk2@jedjAT$iOW8Y;YRpkr^G5D(5bBp4m0P0bGb]4P,)Q>:H%KP?BAoTD[W"Yg1%L6`;D!b1@!NS04`!FhdbBV3q79_@(`?WYtY>iNa)XC*pVNHE%"+n?n!Q'npZos5*;UEhq3k"P455XYSo@(mh6bcT1k,(sYR(QD?7aqbm74r
N]S;SJc?I+_8W\>NCp'F06f]V0ARZ.;pNE"p=LFVEBG.mY=r.aAkh_fpT=T$b'_(qCrAO39$G<;Y5ALe/'&!'C;=s6DObJ&]Ztk:N2&n6:W@74`e1VDppfA7pc7r`@IKC!\gs[n5o5cb>g9o$qaD*U:C(.j_`jj"bV)@:$)Bq05%?'e@tKgK\o*8mY0IBB=/JToeDp#uRMfVim+lZ71KB_[q`GLgb)KBF\HAXr+E4*V2VO,A)>BCf$q9_[),LE\1>cldDYaHH6Uj?E"`U\QD5%;13f)P`<4AYpABC-',e$1ZG:bLg/fD>g-JsR*-O0+-0$A%d'>Fb2p!+(2u30Y2kIKB;:\BLMR./2'9'a6FKK=//7)k.OJ)n6!Z%Y@`sEJhJm[8P9@$S?b6`#s2]b)kt[j.P,JtfMi@;$!`ZG,\,4ML&@1#5m1(72TiDFPS-Q(*A-rT6]_fI5f'1j-=^0^K$]rB6c@%P9q#g8jd7IXNGW?Vr61N:Z)4`(V#ZXTlL;$#8mbE"#>X5[-Zf'^@\Zeo$n,]==IKPt`oFGkKdAFZ#ns'>23=gQV4gamG5]j+ZkP'):_[D*(#Uq(gfMDlGr9$pJ(!i+_R`'g8#?"PO&RBet"bE(XK\>BC\nnLdWZ?iTt#[oi_5fu0F'\*RBLo1pq)_H1YA@XroBX005Mk)h!#j]P^B\5)V0R\cAseg?O,K]NZ4T,'f#RJAN5X,8GWo@f*hNV%G#Bd:0h$P@E;0JL?*2a*TZG,foPcn&m5*-mTIMHN!RgWX"?%nD0WC]g!1Q"B?>,$-qGU%F3Q@N0Zo4=U]+**&Ipi8;84[3RDR)teL_/,4V8-WrLQ:$*CmV@.Ap(c@)sE"]#,[m39&7r1G8="73t*acT]UMfiXI>Xe)WPkNnE6sA573fWZ$FbnJJf]Eg'i.=#YB%0?3#W//IC.cklqaE$n>/h0=[P4"P#J5Q_PuLE0,!hFJt>6KT?=!UR7jC:OcVdGoYq!YGhA3OVeE5bd^mrG,9HQiSQ:g+M>p[8JjB-3\R%]eS4Z8&iCG@.VLaGBfeeA8B)oTdD;LN7=&pCl-DS/*GCc3YteRC6[Lt,HS)@?1JIU=AYNC3iY>__G;83H5LH%R9!e"=m]369=1?IWUQ^Np35mDk^3-bEBG;ejK\uBDVr`VrS=#WIfIZ,[o=ls6p,L2C1>f?*E//cG+5U6$dC`cb-L[5c0MM1]iL>gPcUkX6u*\pu).R4W?no?Aq:(JY;2Vbk/>p^;8"U"eh5U`c:#+mJWN$43,?rHiY>8$dI6<'s3<>9(<9u,SYWJpZ=a0L)lbq?;<_F`?V*(un:;`=o,2XK53l/hQ481_UrV?j/CD"u_\4#_(giiu[tjE9-/?TRf>a^'q1LpFf&K#cmh\h^d\3\#'9rU;N_:U"jdk+UHrRj@Nl9RLCGo&R_K#Z@d(r*JjUnC;lp-5L4NE^J"mE)5:,g"*KDcaNh^YGRM?7&='CR1.spf^nG!UEA@-M9!JrpuTUfePI[e8Lg;em?+7M'c)dRmjPa*V''lPMMdF-c`FW/pQ\2VBA^r^XAPQa1Y9+@rSXjG3<2g:o&5&4n&^cKV,EnmJ7,^"?Y1)LW7S5MJ,ep"7TsYTN3AT$c73kB?@2S3jl!Z_?(EKa:5i)A5FVn$=A=5")g6)eK9_VQicJ&YIm*Yg`fAT1*o7+ni4'-(S4Y*WHiQOmkl/qc&_O;VnP*&X&GNJ*Y;5Cg:1T:JLoqjX,G"trulWU.FXeI@h4qe&/q-8=eg'7C4;(W,jNeRQoXeoskSU1Tf'\;GQ1^tmt4ZRn':Oc0<`N19nP(hT`YPU8d+$I-3R*W*Q>(KBS&b0hWXbjcUt,rjCYp#;r]hqJ/$k@^jG,+-oeIeCr)UMLCV-YBIcZdQrdCD'qh_5sf"eIV(QT=fVqjOtp2)kTFq"q(JH28G=4oo'*W`)U1I]@X/En]N)oe8.r]'l^Oq0Mam>Ve`D82c!h8m0;(9aIq):>r@N74lgaKB(^"oAg,_AW.1%>1[2BKb_cSo`DF?E4aT,j[TiFkqQ)ZBU%c[V5raRN#BiaLSMBGQn&Dd\#Bs,*@]QOl_k*$]MM?JV7IquLj@=*1J>\fZC3V#>'W>f-+Mb\;X??YYZYtmF3TRE3e:f\85qY`9a"-9oAa00Jf*'K:s:P+Qf'X%>WbGen>hqalIVd9[e=nD0'o6.,^i2rRM0_o#3D_HJ2G-54bg@^MrK4?]Q3lWp5fe[H;K`K3O18qDj7p`DCuBf\.&*'=2XF-ngqkg\("-bJg<37Es9/d__1QG;jGVs26It_pm5I@T2FsQc$atbY$5rmRF^7j4^AjeU)LWj!bj@bec^ZZdcj+Q!_aW#@*;'okSLeUMbOK=fc4#N=-FXLlt_LmFp-4oS5Tcle?m@[lXVD6`UL%_`B$o/d]pV/[mBIU]$2ZWglSWa$[lm%hV1/7XY(*Kk8+n+V'=79ZDkd9"+.U?fA;o6V?rrKA7*CBgLF;"Ko7=qkKfcSI.Hp(#qf?3"+.M)c7Du%ZC*8iF+7Ybf@eW`#+CfO`@6%E9u6ob[XG#U!;=)F=*JRbD6`:AdF@gj:`Y!oA.Vf?n;df"Lg3mTpu.4TXE#Jn]SQh-gnds69HqUt26U8RJ(G%>&-B0>!eKo@fbUo9`2rpL\*+Xb*S:Pegi;W2@td70`EUcg4Z0t?Z_1Iihb$4@iiBim%+hg$?@M>&>@-BH=Vjj,FKW>80u=iA;3o+.2=u$8m+BmApk-QP[&!QMUH`jEXF,>DW`hr"4qD40MeE)SU<+0U1X%9p;t6210D0X+ZDuA+>opY_WH7I`SQc53!8+PCA?NgK'E+b1eI6oA$d=\&,/h4Fbjg$%c2F8P/Wc?1YBE=]RsHlB^A7^>H7KPFt*.41=j$l/[%L_$%7?%;T00Io`='%C*Pj+mAQFfp%8Db?.-M0RtROb:=fCQBIfPDJuJ$%Y"nE2,u4C.l_nJCj+AGD@)?oGX"q,W']'bc+>(CSn6FZUK$=ZqB9A(XAWBGs%`e-C=^\c]7u6m0:ft/*Wije9F)tjRDo?;QeP(Pr\Jecdf_6I7s$`9I`Z22_:fiYirQH=kbk_U-Wh!@f=90qjTY$`^[+ee4<;50b@a:_0t']D!Q8T/8CfFO7-EKDeM4Y?l.$0")kP5r;$!F>EdYX'*%7MPFhI5Xpps;#Y;:uPdK)D5'H>9r;/G>'@Eaalm$*S)W\HoMs&Do_>6'kSpkcO(=`jp^'_Gm6=k1P$k?Q/R%FZQhm<9Z+8,bSd"I3O4+HB\GNYEOOin(`1*ZSIbZ`e8?u'[C(8VHT$#PVC4bXS(PJk!$RD8iM4!aAu/\BHM-:S4Tgsi?>f.]716IcDL:!TpAUWmGToFbX3=^@R_Gp7W-g]??s(-L.HN4SrI*#;JJ@FCV07kcPWH%FlhU/TOh_KmfY)tIDch5E^oda4>Y%^Ti-4SA1N;ON%>64];Z?6@dsH93#6R_%m/sri5+jrps?dTmJC1YZ\^@i'j%St%:/>gphjkCkJG7u&h1\adnQf`ip!cS#CkD'k3\(S+U,/I:5n&A5DAXgo%B'Gh]G"/E6jQH^]4"c:,?6*A0"FcjRnX`]*J&sg%"Rj]g0B":+'F*%is<9HcMGYB29F?RUR^U[HLEaEd7&5Xn_%t94p*8q;II:7ueRXRe5>kfEI]&a?,3koUb^u=bV/hMJ?q]/l-m$TZMGO=,^o&V+IN/4ZpdJfFC=R\T"72AtVebr\`=;K\NN5Sqq%X_L`;#/VMg1FY6PkX!68>+O-`(:d:rntq-%$.FI(>=jjtldQY*c0peU0*(%r!HtY$=M1F"(j^R`jV:;_As52ih#mS^/SiFI+OfH2L&q=tHIj\HGZYbq'X9"^EsXJX%k*@H>mkCoTm9iB,^T]*&=@0'-75"ViqRE86.p[TK>XDGd'eC34$GVa,mT"Y6F4>49T0n5=VZ@">PCiK16MQCaP:f,r'"j&@,TD'jFt4fsD,._&r.O6G[Ut$nb2Sq7kWG)0Z<78t\s<=Xk.GKVS?:5+B@N+Bgt$k.'+]RFPuYSnMu9o4^Qj\J;'B+%UnR2/R3HnY]+U54'<4l<@sH5cq,*[Yh,PRu4SuqKrmCZFik&]hYWmd.2?^XmKlRBp.RRjQ?"sX(j*PB,CTs>/8Jjb"V.9ae<@.#le'M:cSBhcpZJ>2(FJNBcjMqAZIAeCMDSl2^fqjlpfeKbVFld"9skF^r+S%E=3P=)N0.3Y.8RglQaaE'<7c:E\)UI%j,1Dqo5Xrc5;/#pZlcdTWTce9$7_KFrZ8EFrhB)38>(@M%[GZr`c$:r[t"A*fo!'T]-r/XIK#d<7/EfGWQ>#+?WLJ/_H+R]oLfqC-3L!7FXU/ebacFh/H8jYWZrp!!AIoLD0Egf]+Y\rhO^T2N_]nF1:K_l]!2UkIq?4OPEHSqP-nAlAi%s=\i=kdm/G2D(VpdI5Q:rSl,hT"RFGVY]*Qf7@i`F>rQ7E>qIV&n.Q(`uatq%BhZQL+C-Zb'YSc^dXR*/.X_%[NN7F[%8QRNV^W7ro[>R["!SB#04H0G]"tuU\EqR;r[7_a>=3&=9-RM<4rT2)eRlBcjZ>o?AY5JGcqmQ7uY^bTVXjJLlmQPr=s3[TsNum.FW.O'TmE)taVJ].h?bLJglGi&/d,<"7'jP0`I-AG$e(;XC>ogR31jEHoqWPlmn?o<`7Ye1-Nt.-X>opRtn_ER\:At%4V/rPSS3D5rjKCdT\TE#eg>LN)k:`>g3ILMZmA]V;2m;,,\oD@lr7\"=Qd5Sj#^Z-cV46(,Dqu8$*#fd0HKE<-`K?jp[jEJo4b!$4qUs'Vl'K_oB4e"Q2-_05FSkd]m9TLM$)/h-54F?,baFjA6E88,i21091nY<_*Dr%o\2*UWkam59hhn=J&F^t4:_qT+e]mBGCc+=L)5/)CBq!QDoII"@c[1-qUZZYC?p*jLd+'5sJ_W'd\Hq"hWGa.;#=u^X)6,q'nS[!r6J-d@a8kpE>l\F4Fa@n&A]8]rI>aX]u//4c?Z8nHFRtR7("[8rP_%8hh@s=:?+/don227BS+[*'lAEHf#>^"&HlD>1n:Xfq@LL@nBnhj5\LI;NiCS2W.0O)I&UW;Eb9tI0;1.H=Z[5>?bK3=5+SX;nmE=@j`];g?e=TiQq(?H.Fc-7croAoe1FL+M_lS?eOh`fn2Tc(]uKMb!FnJ80a=.Clg\O0R>>"*qI2A5?l`JqZ-cZ-'_4i&:mV9R@%_IMYK(4pBI.e$R,i6K6NNh#f,]#B-$QGK6FDOMtn[Yj@%-@BG093n,Qren^K>..()9/&ST(F-IH%R?L5h,"DMDcBUe(&ifXq`4e8ap!8\RK@t?%)"b(].s*,%hW_6Kr17FS=_lT\j?='=B-/pG!Bc6.)m!hXae3.7\:en!BXO6QOe=tgXK>g1$"lEc\2Jhr<%KH,h99Hf$8i\GHYB'W[5'>h(m.P^M&Mujee]QQr/uSNja.M^_lq"Z&K@M1MU^!l3Klr9CVqShLlXJ1s]/3K9DA0g?@8Do)+YH:f]H=ionl9h*X":e>sYZZO;eUF+J(T=.t#*R6,e@d@$7M9sR_^&IkoFZ*RUJtlr"lmbE4ELp8dk<9kZl6Qqlu>)%*U&_,`_NGNkO;O!YBm%H:u8pLbGK4j]hT4I#M%L$Q3Rt8V^JsK``:PsKI4!urm;!&5pi"MmA6t0XOfig(j%_WZ6Y`n"cF89Y,`N+HJE%QeN$Yq$]iKBZ[X2je.7nnZ$OHVDTI['$N"mG[(l5/mkO-h/^XSE>^#^(c[+.YkWJ:(\WpD+5H2RZpEeOmUe8_?;F(tus*q-!2-k#AHb_i(o2p%s,C?JsAB)i1W?As:9s,#tb1/I#K1&QRI1)@4"J>&>UWC+OR@<\%&jND#3$/2aQTN7>%93Xp#ahnB.UX1Nf)n%\jrFs9r9C7\I$ApIk`+3,q`]:u\j1u`VlfnnGumBLF2:pA*MKK,-Arj)mhdTid15g^.NdZhl?BIHhgORdt5JTO26%h@L':l:u.#"i!T,loKs#R5RUsY4JUY>$i3!U=?)jHPY-4mH<7*cd/;:f(2+je8^Y3d&R[fP1]na<+@dP3:bL=Go_K]S\X%GaNT*6)V:qT@X1Wa"#H39IdS45?BHOeLGPUMY!`qlU,JW4*Z5/ZYnm8PJ=tcIf5"6s"2K1/E]hK14s=ng3*5`nXjL8W--uqmD8WVY?sDcD5p^lXAHuO2"ONKF^#^hYYM)rqmuY)kB%W(QY3H@>`6Xt#h>$8Q'u`g[>,l"!W4'1_c$?'[g,uk.BD:Ye_RFY3l4,Tr_qQ0WjP=3k=PH><%><"53ehl=9;S:Y_B8^TK-t@5cVd9mJk&K"e;f,W433V`(YS^$OUZPYLP76^iCsY-5j2^>C/DXfZ&M+lb5Nd@rjcnB@cXG_5tsiP1mU3orBo#bu+e`G.(1ga6Lha::#[0XL!dd2k(8L/TV!SR6_]5NGpi[?%@Ai&TH6cTgCr42X*&IT_),:'p%%l-F^liULr"lJHAi6PCX'p!l-+Y8s/N3q0?07c[FXL1^g'jf;WUF4AI.7#%#>(lLikOqGscNb2K!0nLP(MoHKVrs*sEA#CDY9ZMp2;M0kAcA/Q=?QkEm7,ma]`aG(Urh2o=>*ZuVZKS6k7fTIdgDN`Osn+qnehfmBA,TCX0OGs@qL[R6a+'Y%Bl-)(Bn'%2f(Ou`++@DY#,#K3#,/aW.6\@N0\`G+ng`thQ;CRB6_N[]9S]+6m5o&+m:6\G3(nd`OW,'%*3=uQm*Z62CEts(9bHZ\pE'AXt&.As9Q'pSr2O]9b28JT7bgadU5+dO9_oIN5U_2'T"A?9Dk8H%)!FPo+5#,gIeUYtI@L0*05)D[D_m-r?G/iOh>V=L@O?6E8D?N-'&&?Wg^OEqII3!RO%a<6*/?4X1!'3G+F4"AQ)%ioYWrX/IlS`_:'_<'j5U@WB8AkN$95MNs0V>i?T$b<'X5/S(&](3gE9?)(?Mp(SV!k1'oX@.XBh[1UE;CJ--r?!:$gV,O3Q;1hsFrp#NB1Y)QF>SG\f=Z[O]=e[EdhWNH`Imh(3ah(@opjrmb%h9S9^%T)jLR\q1qRZ4m`.TtdS8YIc"Tb4fVZ,sM\+Yq&-?sBofKS!N&nWaKE@"=0Z8'EV:#2RMOU;?#96Mi6!O_Wk-N%VUN!V\u?PKON$#kg%iKieBV1K68ocUpUC/[9$[ANUJ8"GD]+#n'3>:PP1oBg`31AB)J<,S?$/1(=V[K%C.F!%-ln$IC6,,H+\_aAr=B%l/oBte30pdSU7_lmH76:!$l,T+InnZ3V-n2nY.9[k[k;5p!N-]T(^:DA#3Fj-.&ZNOX^T(]5"[:#uGWBXdmYP%#1a7h@)oiaQAAB=MV5LY\XP:O&[ZORZrUsJA;t\oD7.4%9PEI([#G^CmL1/S^/SGp858eM9RJ[?YMWnbZHG"R>0g$q@OVQ=1DkK/t5Rkg(4WZNgpP1(5q+c_c@Fn+>dB^h#tl_/"gd#5-iU1SbMfcG!:gMBo;1,1%u,Em#.rg(3fJ,9CbukQlD^[(Rkue`@9_[:R1-,*0Ml>.Jqunt4BsL/Y)k9$o55(TMp^ck_jt%D!"X*9g]X7T0K0)rl)#O9-f@+/NC3U;ds\=DUVZp`*ch0aP>E&&2.aC8mgogA:_Y_0Q]=c/YI`*tnfFlA=CWlFE5f3sLV?up-[8()ucJ0flC/Qp;k5VShR+UWu#P9lQ/hHD`I=OcYg4qA\';4Ks,-5h`VH0V3&X*&>^1I/psLL7ARf%W;fcap6j@KoutR&,jsn*Y\%mV*d,C``$/r:\DP\6"OQOIuF%A0eI66Ugt9\\F#6XWJZ!6O3L@QV#T3#N3?^05p'?cM,Ku1%;1A+o:k%TF9-pYR%K@kJeU^SP^jsE:0s@"W)7\&=IRO4UBe02G`WsD>H_L"nSk`7p;4p[+ph5]mVg8F1f#?Rq#=jo=eC)DHeQu7?gkQ5(@POIud'$hbB30!mWT0nInl*-gfcmH@+dM0]6NZ$d4+^$pQ6*E)icfo(TYi]Sqk@UX^?gq-e&KZCY:1+7,T&I]UP2VC''$Pr*GFu;8;@#LPT+'+Sesb,%>Vs`(DtBEZRQc$YE;9$>\\\GA/B=]mfdo&53Ec'XnSF[,8IoB8L;4L&YMromDHn@=?V!<1]S.1c!]H7$QfOCSU-3RlGfBj4`E*8A&2f]3.6V)@P8c/OmXJaf*+ZSr-#`K&m=o(,&9*#-1H'U]=oDC$CEgcEsg[@">-8))!-Vq(3m;"[@W6LKD=omc2Fe`biNQ_VVK;H$KOdQtE.dlRTn[k]OL0XP"K[!T4O`$3#(9ZB8TbL<:in<#L*X?A[U]+c5`6khW60>"63$ARJWhgSNNH.!;4Z8&;r=;#HA,ErWH/nMnS`_md)6%1dY?%9"ajF\pV.k2\qWkGkf:BS@Bh5)5Ke5C%Ohp"GcghfI*,HOh`k\Co8M7La&Q3W>TN[o.^!/Wd3GWh`Y&%!CpaiZiZVT0>9'bGAo:`"o7+gaT5c/1fA3>C?J5eNj>W?'Jd[_P2^WWcb8Dj7Mjdc;bgT`;4fA2skhR;=S8K9apMk+F\q!bXCP8HsDi4G$*/>!O9Ap^k&pi7G9A\7AlM=g_-"f8:=4&)Xm&N_me9:P?'bA>,+,j\r.#/0+1A2qP/iPAT!ciE1q)M5lh?O4C;P!qAlMS*9L4EN8Rd,;6&O/a>Z25BfB*[K5Y/o"35=4fRl65_-DSYN)b:-9f!=rhG8C'Cc?RE'ChnMQ^rUeq^i!*&=Kro'KaIo%b.CV39D;>a\&533G',fWpn&`B6>kiWQWj;;uO9f$6.PJk-D6f`B,k0b]0T`bqPQ`O#B0m^Hh+Gp>9*)PWU94^A^P4?%aB![1O6#&"S?uKMUm\OBqg=U[c:69:hKuriepe\oDWG4FLGm"^a<@Lg\<51XDXNCnnX;Wm+68BUNTDe;\ci3Y.?Z2O(3(08lKJ?f,6V0?+,$n,`:YW_E_-9tnfp3CpR:MRA]%0)F`2JJ=Bil`ZAf"!69(4U-PK(mFFD\W>;A]8!MMp2,d;+#m=6]r\KI,WKj6[&MC%#g(:_;AI!498eh5-+bBP`5B5:Q.*X-sEj.C=9L*AeHPB3'=-#20n8X^YI>`roEZ1Zg!-F.b6R*-YJ_("1"lA<6hD*.om&9GnVIS0FF/>$sC?/f?N)Wnc&L=??bpK",gZ6b75F/>\2&3c_:NlnS*OY<*6'%on8pIu"I3+q[0(ckS[$rj))Q5GWb`.aJiHUCi7+L+82M*B`?SKA6o^`tj`@D10a#&.->`1nqVnrB`*t>,o^7I.)%:Ruo7Glg.@'I?^6cq!V8O7u+MN%P]W!&X^_Yo_lg9)t_ZS\^<;n$KN71gV-H17*_P2s\IS`e!8&5qsNZc5m`'XI"7D40DnL(e1(fg=G,6N;>=Uu612QOHZ#:Vmd3mcP8eCX]>WFJ.T\=:ejH-WNms86h?5+fl'd$`*I?jT+E2oP4#_u]tj*i1Ct2+c9+'`TK/j\$:>Li!gqYg9ofoLOqOo@"!>f%63",OjXrjp=4aN[T/.)d_?L);6fS`0M)7;oK^XI,iI6Sg$_FDd0BP(4Gf8e%>d[f/]".Y4):P[]9a"S`:71R[M=E:Xf=sT=++db.6++]d.tg\?fuc;hn]e\`2gV#A5?2n@PNuR]0.,&(P%G?\_6eHBe8*,%)0d1@Em*]nDNrb=SN(n5bP,q8>7k[MT6'.gF[?$jeSMk6Wi0T#;Oq'J(KZ1Q\.I\KknakDQ`GmR^b+q/eW7>6_?\@VIYdgeU%#icWb2bZYOc4euuRt2D\2](5o^i+d:0I&E[N0$$_=ufXGV)I.Fb]*8[mLoDg8VOmU!9*BuO[i6LMjTYJXZEU3;q9'T$OnV6cirX5S_1^9&!Rn>]RXP:.CR-W_?pHH64p$Hgu:/)]Hhl#/k,&VY"#R0FlCj\e:#E%C;L336`j_f,#Ap"$O[?@V@m_u'1BqmCiX`'UNaTmANlb/FK.gr9C&C?3n:[-lBQ=O"j3U@7n_08QhNDs"#h.(iQ;W0X$1AsCq\AN:b2AuAuVT6g,$[EELE\pF1l]2Mca?^s0$k4OqY-4fK*0\!/jCEK>dA[>9baot28J'Ep7]p2To[Z:iq7L+:i>oeRR1sJ_MgSRuO*e*jk9MB?p0/op00%UIW/H0gaHpH)a)TuU[3A"j,rn-3.C@W\cO\lbpQ&[f.oa9#VnO+,rf:C19L.R'M]S6'E`n)@p*,O&H[EqRGReI;66EKqMHtp,'chlNO5!=2haFOrJjM^iKlq]Ao7uK2S?]lqc8Qmd+YdT"#2;eaa4ZpB/e+%`(mp3U`U/k.fPSl(WlB@1*SPYG>=4:!d*_Q>$7O/`Nn2.E`.!;p+pU2.\*P?joD2VPhcR4S&F%`O2q?t,iVc_#>l4Z.L$l+9\l\,r%^V!*\(#JQ"kpN9icf=fohNsSS^OC9@A9aad(5shqs&L26n]4ni8LBJ?+n2nK%7]ut@tu\-5C;G:;n$]$d*FnQ*qPgKasEJ##2qq"Wf4-F,C(=Ke6WLJT>/'VjNg:6&R0t5MkaeemF[(t8M'&RadChnrkjA,cCKBc+4pQ3f5OF>X[BcUE>*Q1pip;nRa3=\P[0W?7-g-o+,*R'562gM<=b/QSii$_51b]F/7n[BK#i430;I95%>V5o;1Nu#IX"g(ap#t_m[]:!NZ[,-k4mnN;s,7Y^DHORfLS]0_Wm&XY_lmFKk4GUH,XUfS`!5L%q)990_R^lfIg1H?m3:?=&,Oli-r[EEOL~>endstream
-endobj
-4 0 obj
-<<
-/BitsPerComponent 8 /ColorSpace /DeviceGray /Decode [ 0 1 ] /Filter [ /ASCII85Decode /FlateDecode ] /Height 127 /Length 40
- /Subtype /Image /Type /XObject /Width 143
->>
-stream
-Gb"0;0`_7S!5bE.WFlSlTE"rlzzz!!(qbSR8NL~>endstream
-endobj
-5 0 obj
-<<
-/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
->>
-endobj
-6 0 obj
-<<
-/BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter [ /ASCII85Decode /FlateDecode ] /Height 66 /Length 9287 /SMask 7 0 R
- /Subtype /Image /Type /XObject /Width 239
->>
-stream
-Gb"/,LKbJEgA_\tgGFaGf3DfHfJVf[@Z;;0M:&8''4Wj;',1RrD^a0Z@dei)@KT7+Ysf6tm*^5nWQVU6ZL"m`%c?a!R6>UnMKIN6q`a35@:.@)S=02tHerr&;d!f^45%_NG-[=&lsqJ*fu[!4ZBLjGA1'Ila\J+Qfqc9+,58tJieP@%+BZ-?;>=iZMo*dIJm%*$N((3O&a#Xjh!m_)Ge!$VOC0t*qsa6(rSIMM[sECfQBjH+aoCEI#fR<-iRJWI3R"fe;.5]'2Wu!a2!$0Rq3UhWBN9*!k]"BIfdE.:>&W%f+AMLqLgN?%FREHJH&FCMP9*6)p55Bn7d4KB>fA]:D/MTAq>5_mEI\#AE\"KCe--lN<-G:TPB)]hq\HSZ3):MQbN`l,pR#4f].kP5QUao#=2]Bp4"*5>UM8!`2c`31(f.P591S3Tk(A![TR0?dnL_AbugOeQ*-la%6A1\\O@l+*aBGsQ&C%_],HFSdm?D7&&Q]%#o%#n]lWn1PksA+).LqWZN9-\j+:T%^^ON*6',3-&'/8=/`Ug+>-J4.$sW]$q#V&tL"ApWkXm+ZC]ZIQoN[:&saIBiO99XVcfESUGkplWLC7Nh5!loF0DSact+09[JgZ=+5_4a?$na%G5JF9AVE1bg`2ui9Q\rjnaWf?\+VbBKe7cb/E('V0_ue\!EM5uEkQ`X@e<`[d<2q#8_Uj,\QX%E;5P$)_.2$@utmh.>7/*cc:6sPFL=kmC0_$lrd:eA9EXVe5RFEV.WSo$D%4ik*pj;h]Df@m+e"Is*3q=s80i$/mc-=Eqe!sUjdVAEi((Y;,,7T`[ji/#Xng`&QU&bQ5;!EkOc`?j<_#_jZ.W"Ms4PECCe*U"9c^qlArQu]tp%O'E>ktN`j%8ee]B!jMo0*#6`?9VuV3;a0m&%=U;JO>4.T/1s\`,`:QJ\V)1p=]%nM"lW+^DB=ZXKsQ*#[j.u1ro$*72R>.P'OkIg:Ea"6ugW+J26K=m"_Y]p/4Wtl^*PK\JGNs;.>caVA9c[Ioo+:nfhU8O?e2u[-oAds7"a^c;KE?[g@[2V'oPCe)TaH:e^okX-R[dDBAAn>(pB[-3"bD9cSP34bJB6=BMQdtr63%BKE2il-F@55;(Mb+S!/Jd%eQ"@s%4huT5@4)r!6:j+B(lq+qchc8W*#LN*16MOP3!3ku"QDo#;=1!bqgoP!$A%R$_m_"Gh^/WXiV);P$=foFOA;Z?rK+q'gRp1V6VC"1B&s+3^C39:"K`P[r?R_PBbQHf^M#^\pWO;D8Uu1*eb1/gWB?DtI/$#.#I;"%G>VK7`hOZ!NuLT=W`5uWhKnMdY$LYjo-oHH)1ASk):Y,VebZc5i)[@e(*][mA/\`6!m1^Wk$?aCfjtoq."6Q3`,0.1.8>3.Qr"]X`O+P:I84g`Nk;Ql\f6P:*%jn(b5Sq#'Z[T@b6@tlCJ^LL^jU,[/1GTsrtb[U=)Bdd]E6lJj8[JQe.r_'kHs8;W+?glLdW^2>h7"pf>qQJed&HE$MI>8;o_"dg)`Ja)4"[UMc63`'.Y_AEG1VjQq1ZkP@Eg4LYc8nF*Fkj8G-=$rD>PS(MLncBMhS;?;?4l+(Z'b)dHgm";6-'Q[o15*MDr/T:&p]B^)JPp`1T'`/kh^=F;jK&*%*I>&,jQR/Y16!I>$.QY/L'=M%q.#&%^G>iWh4sQpiQgBpjWiXVH^%e9u7](bI\7d&&cS;pH*D=R77S8/TVN_-&33DR!T2iH24$e7f/-*A:\llb13Eg4LY9(_mJoNBEQ32[F%<@Z?tq@LS#9B$[i`BDaN17QbdcGkBCe&ekU8qFK]S/e9Y53,UG"_og2Y8CT>Z"32,OrY.LndCl=[OF0]"N^Lil;V)\Y+$=M1q=SE9!o?L2n+*>#l6[;h0Vli#]VkCHsT*9p_f537P]>PCbbjI.9I#U"u%C"0sQ@9HF-oiDtB,Mp0OOfGK`&E@M#8#0,[00f7BNPWR$Hs&cABXZ?o*.(($6N_T@pQ[Uk';S*Ao6F<*I"!-;c8mbP'jlK%!gf?h\u'[5c1qr@tg&>c#IPPitjScLpOp<+bL&sh2i]4c_o9Z&ol+(#cc"MID@*a4R?EeCgKFC/@S[p9W"Jh)Ui\eOU/e"o/SMXddNW]r*iB$m0UU*lj#+!je?^E>ZBk.%`r20p]ZejP3\\WCnd24#nLG:1LI0!c@'MCQU`LDI+a,(34,onD.;R4?\i0ag/k1X8"a9!7jk%:1E#RDaL;0H$Z4I[gBS=.KbpZ#@H04TpDRE%*bkrh;j,Y-)oV?ajptaiWM^UXSI6Ga-sqH+n`VC!b.*=iFKru2Offt^Z=^D^g_r3gViNhTu6"g?%bD&VPC5*QlAD4.>Z(UnL*\Pg82,^mbE3sf6:JsG',Xah2Jm3\TgKhd1qjFsr,\W1<_cW5`Uu7V0C@KdlJ)('P:CX)Y,lHpKF.FWg5^4;hOSedl_Ik>rr22`PCE;#\6)AF%_m(_T(dqIi7?Nsa1trn/R,ckqKlqf!Ji1ijFGM?b1f59fR!TMa\R@8Rlk5()dR`;@]d;]00P3TPni*]Cto"oR1-VT;6ITOGD4Y00W(ccP>%[Wh(tJ#`r/Ab1Cg$07<5=B&!U+c==hD']Y&G/*UEO@BtJa]I^&JJ1Mt$"Nmr%TCjnm8rj@1O3r1c:bS6@I4=/iV;^Qobd'"k@qQZBlbb[?jP@#MSl`*XZ8KaL/(pN5_TgMN!pc>iL"AA/>eY6Tqmpo-[ajg6++<\dmC@(h]NQ(lH>SGAp;=7OIs^8l*8%tmLY+S3OP`g)9Ma`m3_u?cu&r?%&$fq%*V(/N3F5N*tKDlIlg@-Erh[`a]]=j,"[6iGD#+j^X=BQ7`.>]K=Ue$q\`-TQ`X*6h12?!K3>$K^$n?H#A/C-4IIoYNF%fYmc?kE.4p*Q2_m\m;D0lU1We-?K+?\bN4)CkY]MHg`khJ`e&J+!J'L1;7"E`4ILtFLk.%H_&m(O"GYbBS`31UtcH+3L4a&C,L[3V3!G7uT,W)l4n)!`PMQMD3N09!B"n&.qFdbYjM7$B@E0[e:.k/`s9jY0M;Nfg!j9o(tALb?4Pj/m%'c5jQ:SO7Dr6"?bLR4rdLDSlH%b9oPI!%hHN8aH^"UtG'`>B3a2E1rj1![5'?]YogSngIJYt[HE6HAeHY.SrFq3s$CGV;K*68l!Km_Tkj]*T9;nXqC0"F"MV82k0*MD>^`pS)L"gI4UsckJ#P3[8C#UA#`EK[VWK]"mV_?6s``1ge-_V`n&%QhC68T)sfCftC1R\G5ih3bdmOl&>o=hYm>>FB'_'F^M(*S[f2]=oC=E/fWcUN_:$,7_85B/tmS6H[&(q>f(M%ZU2YJb5jokZ5$#g[B('H,;A*/8=TrrMV@UQ?g]5dFS,nK/.j'R$aFtd,4W@(B7,[=K!bd0,\XGY=r>p+">5ZV2qHrb7]aMR=Ih-+KaNb0YP7s%0.?[C>X3TjSp!AkYVsOX(@ZruEu^L%j"H@/b<7ee_Ykftkl^g=8n,!jf-kT6fAhBbYV=/VKL2?uD77S_FoG2f:%.MAM+5X!s23[tQ,W!3N'ta@&4nUUkYUoVM;EM\j2HV#+Dur'Cm7KB]j=A6.C]U,kTdKP7J`Rl4crm_*sMh=$``fRLU1-=5TE3d(#cniCd]A0#O^aH91%o;;ZqqAqXppP&VGlDr/2k-4>o)A@MqK+GD-hd67JTj31AAHTc='BDaM#qu48pSSa4pClG#DgdWcd>"Ad"^9cZ4Uh%I0j(gl='I$Edq^47$G$@Q*KVIi.p)kNjKB-hOQmi`[1&BS/X#2Bc#e>AR<6)'@mrh7RT1f%6Da7d/mTBqU,2qqQU4"WjN;?U0W1eEFV[!F[/iXLn7S[aQsg4k=iXV);NMV>b1K,b,-8u#$W>[IPp;Zj-lR_J7%`!lRu!5hh+"1gah0+>l]D(P69%W]kD[7Wam0qk->E&ZccQ*fE6V.Ygt`nVZUTIJX47[u3jb0g%!%mtYE#Em`dq8H)/1s$9VZt8Mak'F*A(#ePEj`FR-U]``E$:Q<8!#9[Zs18b23P_:^'CFNTNJ4YVQ9:fLg)tmuT_DrqlW#k'KK1g-cPlR)bTV2.gT_%=g4aFml1RFGpqtTt]qt]Ce?[XeR`/+8s#7mXg5%S;:b?(g7qU86T3'1bbA^rOsM6[j'O,Y)+9pno*X%\pF]i\"!ZuPX+\-*_BJ6/;74,o^h&I3bDGqoIXSda*uH8ob+r:o:g,=g0T34lcmBCPKcVYkf8CPY[+:HRNDPY4fQ/P$;\WR5U3!*VSc"k]a%;A/<8E!n:_;X*s4Rr)UN5ITrep.5cTGV,9tB/U@R`1m5GXDBkT6#jmfqcm7qC/^N9;Hg@;l`gV+K+("II''rlEp_dH;,C)il=\+/W1)ieiY2YOY$N4nn,0RL!h;u@+LCEZEg/&]s)q?^HYq+[=.5`(SB;S<='R![=cp%cXB";iaegiHHUM[$FZ?e4V9C*Q,i)\N$_A:q`S\^Up7)br;/E%GTE:9rd7%dMo5X+Ji!eu;fnCc3qL^79ec&c'XN:1_pB;aSBW8WjD`!H1KVL]d`^(B05,:%WB%02-W)=:P6Gk!'oRpd_$&.\]r&,im/fAYkbKBI_KXpPt#c%VR3&(:kpVBH.0uT`\gJ_a]>\74s@jE&i?G?kfeRWk]>kX2LQU-qe';8I&q(TNoVcA5\V.bA905JA[/fb3Ar.qIbL\:f_M%T8pa\;O]'bA#]ROc;e7H\kKUDSUjlV5#`L9X]kRqu-K%Mfg:*<&@2[]XK2aTbAeL2@Gm\ZmMmQ3d(Y!E/()KJb[mh0()Eer#L7'"Nrh&X5_V5[\,BmO)s%CRCHp*TP*tT[ApiiR6HOKoqY:qE=%/#H0YV"OjkC9A$h+ll]X5VS%dd^nA[1:EM9,]VkG<7mU)oAb@L3OYr7NM"c4Nt;Pp-7:\m7VhO5,$L;Wrh%pK9o>@:IOnVK-biO9;Z>5)iC&anu97"nK,9ZI4CRbV+LF2X[.hjVa:J]Lq+;CA>=/"SYbYC&I7Y:aR?Y?ih29dk_Ic.-;.\ZM[cODAZFdH;GQ-0@Af!S;QGV^ii7R58K%7,[t*&kBN@RZf7bc*hX^m"40d4SsC%s(!d?A!sepVAl.M8Y(Ft&l7:-^Uu<[d#[9tAG9=+,UV\CONIQJB@n8QiY,DajRH_0ph[=9?n;;Y[m\rSTb8lo:Ki"_VA(u:_4[5Q*QD\biOK/5UP/:fsNd1=.gpYM?r\\j-]nf4f7UeU=0[WCc="),FDDR3T1]?KL.F1h$kVk;_To/q%Dg#@'%[L"DCFPDaKln3<`IodsWkp$!q7?582po>)=hc1]*=OT%"?7C"]p-9O:6DK^iQC1&u^DL7&km-fKi#BY_+2KgQI+=j._^QT)'rg6n6lQIF!8patA,S[a=YE\'O!7"),d/ri8u1VWOJ:f+Hs*V0YoB;\LCRbS_*dWB5UL)-V,#\eu:Wjlk**#7ho*EUVTf!ZC#g_eb>D',gqjTKtDEKaL2erjb9%oNUNb!#rLX#1!h5-U%d@eV,@4:2\ujTj/,4iBt[HZ\b4jJeS_#dlgZp]F-foTtsjSoNd?lePE?`F99m2K=miW6aUSPDZPgtB25,,c45.U^,![Lod64J@[?6+JtNN:KCN?>pp`/C2tICjb3SboMW+[WNh45F=q[>IK*8OEc5F>P^*#,Yp:/2J(H39:1I$,F?jfgE45%_NG-[=&lsqJ*fu[!4ZBLjGA1'Ila\I#bPF_[pOAXJ]~>endstream
-endobj
-7 0 obj
-<<
-/BitsPerComponent 8 /ColorSpace /DeviceGray /Decode [ 0 1 ] /Filter [ /ASCII85Decode /FlateDecode ] /Height 66 /Length 38
- /Subtype /Image /Type /XObject /Width 239
->>
-stream
-Gb"0;JH,ZM!5fq/l5egb<<3("zzzn-ps*A+f~>endstream
-endobj
-8 0 obj
-<<
-/Contents 21 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
-/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /XObject <<
-/FormXob.6ecd21492e0a5715746f0be234b6869b 6 0 R /FormXob.c5e3a26a0646a389e1cbcea49bd86cd4 3 0 R
->>
->> /Rotate 0 /Trans <<
-
->>
- /Type /Page
->>
-endobj
-9 0 obj
-<<
-/BaseFont /ZapfDingbats /Name /F3 /Subtype /Type1 /Type /Font
->>
-endobj
-10 0 obj
-<<
-/BaseFont /Helvetica-Oblique /Encoding /WinAnsiEncoding /Name /F4 /Subtype /Type1 /Type /Font
->>
-endobj
-11 0 obj
-<<
-/Contents 22 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
-/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
->> /Rotate 0 /Trans <<
-
->>
- /Type /Page
->>
-endobj
-12 0 obj
-<<
-/BaseFont /Helvetica-BoldOblique /Encoding /WinAnsiEncoding /Name /F5 /Subtype /Type1 /Type /Font
->>
-endobj
-13 0 obj
-<<
-/Contents 23 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
-/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
->> /Rotate 0 /Trans <<
-
->>
- /Type /Page
->>
-endobj
-14 0 obj
-<<
-/BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter [ /ASCII85Decode /FlateDecode ] /Height 252 /Length 19445 /SMask 15 0 R
- /Subtype /Image /Type /XObject /Width 208
->>
-stream
-Gb"/,#?V\\gD;@Y]1.d:M-Zn==A^pK(@6dN8lp>&Y1P/NH]tUa$IYCXl+M;FI.,nT]%e,Ge[l4Smutfp77W/]^sG8bWE"7M'.BU,)@@n#U5'IJdAI/Q$\W-3J$!Z%DF"D]#*IjVGYm%uM_GF`e_OM3?(%(NmXHn,50$;Ph"eIPoi@DPIpQ(PhWF/[Z_Im:pWpB$5>+LaVTqpBnDb`Vq#q!"5;tarSb;53YGjeJUHVa9_O^%&K7%1t-=is;HFOT)R%hMQ3\Dh$$?XiY_j)M+naRNe+K7AfjM/F'YiD1<[%N'VBrm6UppIr)0jNKI(B&-ERg&dPaDTKJmDV::l,Bc/2KArcdUB(4Q'@2abprgKO%=sn--&S;ff:In+j/2:mn#j@uT,20d1S=QN/4\?o)nJr+S[1]'l!V1^h;E_Cdh\#QQp1P4CB[\@]&,3cbc0)5SN/9N=PIl-VsM9?l#E]CDbae,9j!JuG^ji.*HE8T*[)5J,68O,Qog0K+!87;8b":%nf;=jGd3f>gU:.>`.`N9`ZscRZdmE(B.++?J*41[sWiiN?G!-7^1c74=h[fb'jt,/!mt+t.0X(4+qf&N%0^iqFSJ&1pV"PF;6&']YM=[I?mtQI\Uo$79RD,uY\S'Z!et)?NV-r>/Do(4+e0&7c!h3V]""3qG`Br]27H91g,_ZLgJmS-&E!fk$Q%b;6l(FSHY)_C_+drmm,31r>doHWs+udeM8]h\k-0+=WPP+LCRNopNn^a!/ZGO62`DXg$RKCMftS9/)\=9t^8qk@R4u2q8XkCn5'fP7"U2>3fAH>+mJ8md;DY^C%@WO2-CMXf\u?ooPD2Cl"7@/a^,1]OIVThldh[;9ZW9g,gnkh/$nBU99N2#,&^$M[F[c]JN)p,B_N[VSnk,T5UrPK=-kKYZ#XY6:#T7OIRMc!6IRen;/[Y0NXgi1\bUtQe@o7TnVU)6CC;nQPNScH,N2mn1k*k1KWR5_`BjG>H?5X),CC-a+9;SY-)Q&I`rZ?nnC!e=`U4`Jc-Y0+c5qtgY2p'P6Wg-R.023"!2'4N"7Z[[#gpSWES%m69VnK'6H2j2E0q)hI`-6A=*!(XfZC+rj.IRaXo&Wmhh^="Wa-S+J(rZhcdY`+Ku^9j,43^S$g+6,OT1:N=EbMU6&5L;mkTH77L?FRSKX&d+N]cc1FSFVQ_ks,G:Z'aj309AMHi%(Q#"?(_qO+;m!"$&pF8-L?rPu&e6D/a_Jn=M&A?9p>+4ob0:n-e*J(q*Q`T[LRa?9lH56clQD`_D&"kuSdIQ#69oEc_&kRmA!STYQ&hE^%)X=4fMH.#Q2@^TpZY`A!$a@\q$MpW&Deh*Z@0$*_]&+nc;%'9H?P3We$ejpHg=`PTodJ<3cmqm4gU'#somg';r9e3dK`W[\4o'F/c2%"bU^iH=h29=t!Yl9NOpYrJ#65P2?g5tTIM4!jm&/BfZHn#9rQ@YbYNVCYo0aaj>+0/clMM/(->IEqoWt`L=''e"2:O,]3)7oW_S;C@n";F8-V55F)L&_1n7dFR=-or-:b!A##3RSN),*-0$XrZA]8)G#lZb1rFhkp\6L%kRt5ojIG3r7CtlG!_a!rKW&%"]D/)TABc]4%jQipa,sMh\&A%an$=*XM.L;>!g;S3AEs=N?G$P=H*H)n&3mKpo*tD)(?Z&e.t"%jVHUu7Q1t(u+Iho[#H.*uj&AUi_GnjsPoW";0mC+O]aA"B[8\$3MEfU3k4k*D;r/g`]LD01V19b*)m;*L+Dr+r4_E?6Q!VodaLJk?`#-FebHX<3KZ6?J(Geq]CrdY46ETn)djlkRN1Gba&-5)C;`Z&+8$J.[Rl*J8pEi]oF%-hhk,/2Kb@/F_irsG>u4:Dn=6El@@&XdJ;!#5"S\3M+$+@m$0u=P8Z?LjD*r*8h`sYD=/V"P"qTJJTFXX90QSJ0SXi9!Y4/+>()\,dHhK)[]bM`sT.u[f&_@Hg4j4/PGPpP"ltP2.Gj%`dU4!9>+Zct2"b,if#S5cq->NILPV7D!#Fn$C@82")6^QGE/g:$_K+)M<;,5oY^Tqkrcl"WJV[%_rUeL=rWN\9j]j[l0@Cl-J0-0d?V9Nfdfps+DYWIC07'k%j>6$,KC8/8,6/4"c8AF.##YU4[:$B/MrlU7Xo%Nna%<%g*P\2]dL46:&ti47a^P2bTca,%,2ALX/u0**R^G[3[Xe$JV.glfP6b4:,G,m1]r0<(rOnI!Erl^Jgs?Tn[F:5f:\M,`T7-AMc;TuQJ."OSNCT3U6B=!f&^MCks4ES8HK!KR2(jnqH_IC"%VB'SfSXXj.Vl/=3q26TDmB'/4]dDHUh%QO]92p&D`eOpk]N<N"Yb'$/@q19[/HlI9O0[(LK2_>-aGKkhaae>2,cL*`4L+oM$.8*%#H(6bSWAF#V6WWckr<'/`4uWlZ+Th%kP@r/QdIb@!9'LS7(Y[bfR=bEGiW_#0;$<@0!D3&Oj:pde#R_JmTNSBr0(0kEr2-soS8C2RmN^LP@>WEi=+"f92V'SJ$$@FUsK('"OIt>a*Eh3>_S-Kh?tWEV6U_cOGgj(0-Tn6PZV!Yloi+XprmS`!VaiXU,%!0^u1q=*"YM4a)_]H%Q.!&X`\AMZPmQeZ(KZlI8EDmLLM/n=Prh]e>@\A#j1a[,eh>7-P>drE^+%hk,$.,-*Q"iqq?Y8=N`Ob1\DSMW2;rsMq`:!UFqNT\a`I*c!MRqGBn!_%Z@Y,_8ujH(lP&,D1U[F]])6j`FD26?-O2J-]"!(mBDd/7J9V`Hi#Hsk6VQbl*PAbXDN6Dn":TXQGdN:J7t8"GU\W_bDr8KY6D_2"mNjN[35LHqd)o@9U^9ikY0_-DBibMgI>9"Q?Ho&3n>dVUrU:%GF:l0=]U.O;jJ/O#[`kDeh:c=Qgg$BeJ"'U=q90g8!_GgKgQH4WEGGjMB3Xng*,4HW**e%P'@Kd=]%/ID)TS"4Y)WF*8;=AlfKS:i='0ehJ>NC[EDeCiN#baZ,W$Vg^RV#oomST!t1hsRL?5VO%LhD(in'&$=e1TSk]d6eoXY^2W*8N5(Ms"8GWk*ErU$nS36:BIdhtI^<,:q+Rj`[C?#t'&8mUOXZ=sn4cd>j%Js&A<31[)JUX,,g\!!;O(#!([,RK:/QSk[C5Zl[T2H;dL.IIH^J)Dn@eK4*XoTY>2-38q+rXa*`#E:$$@"^27F?`mI!'FZtPtu/GCoYrTm$7J(Q3(&H6[.^-=,R^-HMa\?Y.EP*rP>=9_u!IWFGY#&Z@COW`FYkOhYdW1:5"RinJ[ncV'_?U6Y+W_1#+kLUeAq^ha=saaX(=_n+.Z2PI'Zkc)lr45YAE8)c7b,8]!`)T#!W]IBG\XROJrckeNgTr+q'Pg\D'N)95`*i?VB3KLm(dUi1$ph7G0g:YHp7Te.bS/oD#F(dkG7ILdVmF74F50!WXa0Y%5r!d%c9mHr6"bInrdC:QDGpN8IVD19X\bfKNZA6M<"9sr>R+%,L(>uqu9O_EbCFNV_tOLOI/0.TaUH]P[mr/)*u?ANLtJ8\durl[I^`78E^U.Lr4G)m+NV8i1aSk`L,Tg5a\quH!lb&AJMe/Vpf*Qgt!kqqJoH73>U3uj_QMbY@+p\Cg2qe]B)eZ,-gW_Do6@A[&blr+k&4'\(^A[.AAGtX'&P0ulqb^n=%9`bhIWM.qinKH#P'>/q`.Sc_gXkTfOt2%g!X%q/%?XZ>0KsUc%j4r._"_S0o5C6=*[4Yc&Hmh)04(_:-a4A"lpY(GfXp4e&tR=un.:fZ,s3)tT]o:/[XdZcfa&V3!=(Zl?6V`.n&>?MEWCX9d5Rr>O'q`]F]Wg)*uP&3`%"G>WcH\BuEcF^`uKn?-PhjQ&,g:q%Xk,XhjAd+[%tHV9k*=pD\iG[@(1'(^Ia,D\=Sqnh>\-'cad7A'\s.9u]7>NXA>j1K$F_ihE((r[!]fAaL[or"%*ClfW/q-C'ZQSS@9:-=_B7Bo)]C.c0(6XaW2-pC%]fDG#1DU$EH5jGYsIaVcf+^:-'jKmh_,hR:Bp=6NX;Pd'C1-&rsP^3oa_blpGRgFCA['_UeuSR%ck]Z^\1AY?o.aXcN3oMi%(M6>tG9qY-!o=[K0.0I,W7>0f)+mekUU%ID9J7GAsTu@*YA*u\R`WjG94.-ZH=db;"sQ51W8`78BRaBiK#%cHq[('GC"5o?f>.AMRo%pR$''r8C/+$9R=q.V;R7iJ4j'M]fn(a[X,UL8J'>mPHr1l?ij:WN2jl&)tIq==bXQ@KI)0^nR=dSKCfWjrggRuhV6VF3N3B2>ZI]cef_5#I*i5MDX.XZ]i:%kG"l,?b'?h]a9JMS*s0o(>ZK(@5mgK;4Bd&FX7''%JP]bBju/rA,rYgN@jeNJ/o8EC:mZ+d"MUMkP@"HCZZ,["i)XFfe%$bZSjmjU1'@WN-'g8:ne8KYo!AnQKVdUD-f,0\K#MbbRC0a?9<>Ae*fX\At31I4('$Vbt=kb)Po1N[e&c;#TcN4+c?[5J.MiskEAQh\\f/Eu?*V&&.)Y=%Y)J[&rFTdfNmMU=@4CT84(-AVp66]?KsCe4TW?kD>Ao=5'X/1Anf")a9+HI1]B%<*H^9mdfLA2F=jgh)LY,)I>)3K1N.2nu)?>Y3@9EXNHQ<)dFY7rj#8\$`^6Ftjj1>q0,hq^nA)<\gStTs%<$Fs:+3A(>.rM[DuhK1g<0FqU@.L+ps3T8^WlE[;#fGrU2MIqTBlQ4+p`J=fi)^W;5hl.C*7ooYBj3MKj6[9l90(H:8Kl0cr%6/FGK@g4"&>6Vj+%;8>"sGf]2m4S'D%"3s]j:B\a?5IcN(M?B@74ai##ToP\Klon=CQf9=fJb#Fq^_N2eN*@2@/N66B3`+pE9;E'8D^V\$a>K]kM8#qpS_gBNfCtg@#g?aL0g=K\k[.RFk#$`XIKq;2?`Z@dnobdc)o2:(T!EOPD34Q<_R$V*:J:E#r^i";oXD-]pOEjo=BBTqt7-/2KB]XJCYW8VdGlJ4k>M`JC#LhccX)/0f4RUpI,n]Y?T?OppP>Vk*CcC@<9d"&s8/falZPk"Zd[M5D;>B-90e.4d&Z\DJP[5E%!jJo6]$Io0A5E`6EhFpE7o@XP,1e5cnPiM,5;Oi0IFIs>[O;,%4NP4/nHJ/GEd6i5,QoIIS2sj9h)NO?6!7RkHVNQ]dP(Y1],&PJ5+gi28);(o+Oe/Ui6H@;]n!g2^JWLFNKSs/V\7\_RK5r:,E9q?5*_*XH22nWAog,bLW)gJIp^4e:f2,A+X92W!#=C*cq&&s8?Lc.dFg(u?>)0^U-4-ck+6pZLi1'fq8_%!+&ELLm6=3ZmJf58&9:I%"mCFXk%9`+&*_FH^Wj[3JJ%*&%:;WRpM6\ijX`2L3l$L]TLkFd*DF`",WG2=IWY^1o!WdTPcD**cujBjT`Pt:eSD;4n^`Umf%a^TiD'/,LU=^a^DrJ.&NMX9OC=rBe/u,V:U3PU*p(A!(\TS4e$KGV(O@%a&jKNj.B+:,-NjULa%b+5M5]'hc$2DHsT7O;,KMJ#305hm!8U=-_Csgob1Pm^uIhTbb,uqW!8@PEE772.F%lS'GR*%m,T#!VjV#-rh"=0A)pN6:,SbFaS'(38PS@\(LCDr'*RA>lg3]?m7Sue3Y7Ys8&Zgq`hoUP&Up@A*"e&s#;?&*plKnt:dYol4p.Lg68KZ92*&a!C\gSGG@],hdKL9m/0_cPRfQ!CS"[GM-mbIZeabDs\J42=d-uSDS9cX3k3DEc$G'5@:!#^0:mHLOpS[Vf#Z+c#k"5cu-d`;7/Zl,^m[!dp782=ii=PBHO]+??TZYt,rF"f,X\OeTb"u[$d:m)i2h#7Z;E*NG=`l#ZX";uYgPeL*Goih+3>L=#a^%FgGa)90Y-g@;ZQV8d\\M4ss(5ISq'R4-5T`s/;=8Tl9_R2ZO"SOA)^i\5UGG((BdFuq$-_ZWV$*>@"]fEI<:=L?19O=?gjp_Ke$PS!:c^d8^,Ab%N]8JZc_jr^Dp5aHWaMW\ikM2hV?[/R+7%SZd\ndYK5nYP7R;/"XT?0d8mbbb^G3B:;"ZkH5J1A=-?fsHo0bI,e6#/qir/@Zd@Yp/]^R;H$\pnC08L9J<[QoPR!oHO7),,-5Yh*jQAi4(iN\nY8LC`qM0OB0k6`?=oMc;Bc;RS\/9I=Or!Kj)[]uOW/'7KH=TACbPZal4i8E*YEY^Wq"'rsUSfAeE#]RB15sIO@^r2oHQ!-D1T,YVp_hkH/PQR9=%;e`je*9TXgcItd^-NfQM:O2`IeHPrAl7h?ZNfuZXUq86!Y\\&LQ033"MM6'8ZAO4bW1M*NR&Fe:39Pc+RN!\%$#P1!MY(ZLe(8(3.Vl&-7m!?:Z*!\(crG+q-]_5h!nLM/\lUi?nLpTo6[+)bXbP@]t)EpH(j2J&+t*oW4RuX(]Qj@GE3iWDZU^AF[4>tcdpo0U6!VikkaV[S8PTG3V4+i2dL2\X!DUp6NZ3Fm1EI[n&FH!Mm3(q,.\bdm?qsA"ROYYP2.uiCE%n?/6V+K\IgRZ7]Ar'f#:5;UXjV''tC0K+/)n%YCC;/E78r(UF;1fS4q?A]0U+GDt?c9:NK@#Y.\=)7M!N/ee5JXHWq!P2g9Vsi6o&EHYLiRQO8GUn%uI`/uaP]b-;E(HTq3_K0>\Xa/W[r,?nG@8,ojnhVHKRn_*0fc4AD^24TO]T;]:(-R?NOa4a_oRm-+s9,u'nCQmrZ3!\EQD@ZRL('.P!@EUN$NGts;65&utTrd6.#l2<5?,iS@A1>*K_V?5MHTtH(\%M@XW'1L)$tA"nb$^:J?KV3`g^fa]Ro\Wli>cNXp&s&qLY`;%ls4EAmCP2/IFra%Q"W88/m$Tp483&`=o+?,3=^]3s437,e&J@X1P2p^lQ:%4cRUE\lbCXcb01qY:FZ$sWIi*O@Ul/gI<4nGi#OpLIj*"q446u:u/;W,dV_jLX8;&Z3/V0BQ92n&4i)8Ekur?"gFQ;J:%`(=^L;N@bH0chBMYoH([r4U/KgY6#r?hOcl(K5;s/_6IUS[=uLUiL^;ZN0>F`W'=;NT5Y>]Y0C$Bl((pHol\.4s]JHAZcELT>:0KMd\Qr=Ip3s/+.B1;HurYEf^rQ1Jof4^3"cC_&Ol:-/0h/D;A3gUp@YrK6_W5"THg%h%c*:n:g,3iB=g58X`l3GZqRZ>#i;Vp+0NYN`"MLda,M9V1EaI72ViAXXTIFY25@_ZsCtDH.W8YR1qAN9bu?EY2AB;>LPY*QP50?#[(0HX"F=i>?l5%mn#)RXQ'6L]S'X#2-j]bDXB]23[]O+&X`N11`B4H9eS4I8[H%sKMlJsBj,D'"LHm^`r5B\\DF>?fk7fZ8Opn>NL%m$7]?qUr2fr23S#>YITZ!Lc('I^M1kT,ZJCXNSke!F&]hR[ZH5pT:K[pJds%e&af`*9##'-XV8k`Vb#Qp^)J4O:[YLZa97T"Uk0d*uc0BNHc"OjiWke#V^bBK>"t%Y:oh31F)[&C23U&3D6T!lW4!`4*9s775al"jU9>F[n#Aj9!#&PK@IhR3cB5f:;?RJ8pNThd`0>J:RNtL(uOiVI_@JkhVN_BaH?G@%grd*7_H?WmrBWoE5TZ678-C6s,:@4d*R$VeGm;^p)F%2D;)@RU)F0EVF6>PPI,Fg`C:(^LhpYa`4#f*6l#6o2[gnE\YmHQn?'!^J9P6&hfQ>;l;>L/IT$pMNPW(_?h9-F/`t0MWS0N->O(/*S"6)jn87sii>cW_p`"m@L2nL[cuSF$WTXrfeDM)*QEd\L8%st-"-/G'^k)I;_8j#D$>Qj9_2;CtrFoqm/:ML^V>;c$2>*"prqPonmoHPo"i\bb8os1@rJh=;Q7QI$JG@^Cd;eBs+dDM2e#'CgR5f3Gf3a$P;Eh-_YD+qZ*54/F,/XMqn;T%+rRTf\>uOZ(@t,nM-p;qiJPOS@e#R(J<)hA&++Di]h_7#O4,JlRj5Y[HK=C=sBAA+Em*uP=_]l>Vm?:=0aiMOgGKnCW=e>Tej,]n9Tt@.tV(`8=a>XsP2)I._N(Zf:Zc)\0`ujpEo3'3@;Ibb:8GeBj^+mB8;6&M-'G_Iio?GM'N'1cN;UV'qF8G'h'*l5t-hDSea9,1bF*2nXr[`''bt1WX9n$IU-uFul_di<#bq*kcY$DEOA;Smoh@AljI!g:Sr(4gc#5[GNT:b$#`JYPk!+^V/(>\AIDpZ&DBXC2k!KXJ+0F7GCkG(6VM#E'ZHbO'eVls70Aq!N`o=YtDbVq5_OC<[5Y-&9+QI%SJ?G(M[?XM.-/[hs5p#_EU+N_HP#?f3)](t2aV!'c)/V4k4@c55.-+N"TR!cll`@U+s;daAb87'X#%K&:pui#A3qqb=$fkqM6.`^h'r:R^A1j25G[mfMC>8WN.o]mKJ\roaKTrC5gE^C>_mPTAf)Nu.>gD[;bLI.aBoU$FWLhs_I&HrK+XIM:tT=!GBO$.AVr/H%JAe0o;VVGE]Dn)q`&K'rcu?jMfHTW3HWs]Y'+tp!oL)tj*D86Wq,Ud,D.dX"L4cnAFa87ndm"'ah`/or!T.pDQ^f%hG7amjp)AE&Ia95:q^EdMIJS&59TWfSnkIcolY6:gD*[cl,/+LS^1\e?76K2JX2BUAteHP)]AS!0lLn/Iq]"4/CLl@;ZA%a1QG#_NW7'j5+'c.t0_RGo"(m`ZZ>]B]?H'Y;G(uIA94-=bRJV!0GB'o!Q;'N[o?O0"<']IK:9.\Dlg*k6,#V<:/;a;o_[h%'\L]`j/hUas.1#mlX8NeR(X;G%-kO+D_HF3W%G:\RQ7QHO:Hj.!lt(!8\8WV76?,-Fgt^[cXB5MN5R4SGJ:rF2=dIPQ04)ko)TF^4CIl)AoB3H<#HrY^bEjlsEW%r=gtCE8U:3BW=It55/m>UfDLMEL[qTPj]p0*Fs75aO.$7)b-F"iehL"^>p=X(03Vr[Fce2e89YZDrs.1N-^/;."Mk(\QJan`SrE1Ho*-2A]\_h%N)il(*m]Ck%F8l%%YC'qpfYAO]%!K@)ZQHDi&EXpQ00D>go^r!.[U6`XVYsX[c1gqigCW1jg,DEI,D8#Q:MFb!,Oru:MY11Y>T>2?oCDH=$/4(cESTnP#iisd^NY`Y"Dq7C(sC9k,E.VA3Ta(,8#P6WJ_MkDm]CkeDnl5@$_Q)QaN2G!;1/]ip+I!D:Z(T,TpTa5'c!C(/GVb$_.s?51,?";\\_uo@-=AQ]DIskSePNb+i7!oZ!9>6QY=Z9jmhBgWOBcZ=Te#N>aTbbj*`Z@q&mMn59kna'8R74T".O38a#dl4tsT,UOirDslBk555,4<8W0%m>sQBH"c.'I)p+%`Y77n!p/28HhZBDn%G]!VhBeZ3]fF@DpWMN,t%CUk+MpdnH'g/9i1.-e'cYk9q!n^lr[7&Q/foG6a&Ofg,/M']-7YKo[%N(nr5a0S*RogpCc/p4[q5L_7[f*FjB6JVKOG,K'c8=&&UL\p]io8r\,Sq7m:JZJZ>UUmG-\oI5V5`.qdII55n+SERJcuOTV$"-@@d,Ji`/,-<*&taIl\r=E]hB0]qtQEAp#O4d8TX(?*WG1g&u[6-]c/nJTY!3E3D5\kA\$!uQL`+Z,PW!AEbnD:s8D]LiOEf+ZY%HK?_(LXf1a&rf]*N;5s^'5i@_-/lsI7k=&a!SDh#`2(56sI$9t3-5rSl&j1$6fS%t"I+6\E7RN82!Rd70-9.4H^fm\>U`"@!u+?!VmOlIGRnefebjc'r?`DP!,YL$h:BG4W]_=W&*A_-&R,_HiIG5bR0S/mJ[+_/((6>aIO!p1tMJ8(#`3E]\L)CI^uh$M?';Q'IHn-_Gd2'l"CdXpHg+!d*N8'-:MoH8W;5l-cCT`l@iMGu0,g:/4&[F)!_1ikf8+@bGXs$-uk'9eV-:O_r`8*D.Uf6l4O9lgGOr@&?YjA>D^C2,.qhgP5>HNY`s7$UQ9--q0nJ,lNBGT?L/<^]2qpos._^NSu_Iea55#pbFHJIR?k)D5K)3TpM-k`O`+\$njD_6Ek&X05>,1M>!,9M@k+D/F,e)i%/nCFkQ#/M4*V4T+hMQDM5jGAn.3/R,[-d*N=m"$0kVR%pE$b4X1A\V::iOVo@&*,/hiJL+lEAVD^dLMUGjY@#$c>m)pUMdY.RhnC+:"GLH#B8>Ac$?&>A^A-[qCk*$:!Ql>?gFMF?@gNGJ'_BhDPYk+"0[p8s:?Q4JX&.r*?bUac#$##X1GnHXDpU16S2^PqJs(mqm.-uG5UuR'nDV89]>^sZ]o)T>oh*RPZ^I:5JT'Fno*nqB4aJqc?_F(Eq$%*4%0^5JQ1hb0kE'rt.Mn"jqt=k@'=u[XrqHS\s%*@t]J,HBd3D.(;\1$-%i@_Pha$(n3'fhrrr`2ibH>-_ckS\#n.+lZEFCK!Dr/-`/jf9`^$.BchbmBOTfo,:!7_5&Ck1@Xh.>bR9hc(s!bd!Y7/-0+6Y(_*/GkG^^-UrZc.?;5Ha=/'8rjP@5Y_2!P=:!ZCY#.R)7jafEqeqRK1MAmOogpm>a[Q[!m<2t?XI0IM<:.3IK>-,K0/]Vo\^5Qp25*BKSG55IFuC>VAM&%5t:M=oHj#sJ,P)T,BU#c5CM(GDpYU6,UBHnZ!o]AgHVVi(o">-!TP-K!i0aOD%2ZD99C&f;!8KRh[U_h"CaV3^c4H$n0#:Jaqe4CeMkR>J)cB(I=n)/GOOBWg4&51SP[L+/3j./,H?mai*^q+N,LCdK\Ah@=J,bMYq7-5!D]7NNT=6TG]A33lN&`cKOd:a%c\M&(6BF17*NP5j:8O(VUb3aK3eX9&YXRZ$O\sq"NEA/.p&f[O,W6@N)`.PB]EKG@Ua00\eRi@T3>lZmJ,UEK;flg6Jd$WXJLkO9(t/1+N:@@W$m())&.s'8W`.Co!-sYCd&j'5n9eHIB]6sj)qY)e1Ub=>.#K#Ud4^10T+W%qJ45Gi)hs6[6W1Wa$b;u!n7ts*q"BU0L`UEs"6G&4'/t;ki?o%#;;'+6a^L4J6%c/;X!9X`'emp%;p[3(`Fod)\%W['J:J^pJ>jYA.k;hhF6r*fF9Om8J5O<:k+5^+.k>.dJ=QCkDYET%oSf_;$tCo(@,FVuEP):+)%m#c(Wm#TDl6@$HH#g#j5JsJp(B\TWi;uj>NlDEl[&m3q"3dtUF=`e&3AYJRc2r+B@1kcD6_1KI"O$^aq-d=fe;6OS"$m*hiodHn/RPsQaH7^fh>ecDn-qH4#pebF4p2gsJt0n?K6Q:&\?YYm'pEYp)@]+>6\=FH*/ng_b@#^UNfNO;4p4LrAA^$OM9+d[/XH>C!oP"3='f4inRKTndlJj(#Fhh!9#)iZ;,Ipjr.=b9i@`d;:m`pJ6UsX4%Dq<>*GYM03IbgWCPsqX23j;d!K"T\kbh7fJNXN^7.gW4(W9bF)b?6^R>LVU"&$&0[Bm3G6/cY7[LKRhs7th"Q<@eG2Ck(H7s@gRi)qlM#i0Ad6j/qjKtMq66GkqdgqoR>5Zc-JJ].bN&FoA*olYCbB7e*YP(aZT;Gr*AT#GsIq<+AsH[CA\Km!J7a0Z]mR=Qh9b@!lB@^ZFO"0<)9='+U#+VD5ER??u.TG\C&p?gUZ^-Tog?",m8p;kEE"i95lkHSG0oIl:W2[>4'j'DhD!?)If08pl2!q+drg4GK[5(,I9+oI%/!c&Z:!b;8pDeo`i"]YsWH1Ia42@0nZ"dHngqEo#MfF$XXU^o>@WBPPB?@gA#!Y(ee_E'kCTRRi6"%WfP?`8tm(G7ssKD5Ou(G/mJ)*'tEace)\!lKq.cC=n9',^6K3`*>?9j'iOr;HA%cmk-e_S?+[DhN+SCi$2FY:jE+CDtR+_nTk/)HrcG]KBLkkk]jfd-p"Q#`9FTJSg!+I"UP`C*1Ig=N\EcoP)Y*g)1b)P*;)4/n,LMTQ]hVJP/g3//P!X5^f@$/M/QdGOJ#Q0tosM]koZ\/e>t"Gfq(fOH:%O2F.)o]3d9W1gjTR![Nf.'qm--EdSH'oY"RHS`SkIK*Up6=(!mRKBl>GkS#>r3"+pq(_:JhHg5PA`]rHr?%c[cRp3E(?N#Etk>Pq"qC&f@AD?;o&bH$.,>!+[M0?,mdPk7u"+=Y6/1e6Z8"""f2YE%j+!j[hrMQ5tT):a.orW!7ci?gOMit""DpV5(Y"&bUn?"(s,0GamTs^pg2>"mJ($T/:cLa%#V4)_I])XlQ3u.3FMCH:\F?m&+Oj6^[#JNH;dE^*_*%tX4W+fs.+)!l99bsps7]K!>h%/Vl+b+3;`TJR-JM`f3*s7LQEO%cgnS`MHinaTagcflnPpq=AJ\$pFCRD&)9qRb%H?pV+N[lPL/(=*NE0Zs3fa"DnIK+]-"f`T^Z*+s:A10#rR]AOCjD/+L3W:(!3"lo@n74qou7tf_TbP>1Wnb\=;G!_IPbk@T>+";.R?I*:mjSrV/2BeESYOt'Z*G\58\UsR@=h8XTXQ.%7aUQ=V,$ueWrtj&8N;Iro2FP:]q%?$2VlMB:LCh>iSSUYa'G!*oA=8'#ug@?L+FlP,m*igpB99AQ>Q70BTis'.i:"7hdn`@H"m4K4[]4@6D;siVAY-B,n,LtsKoUW?;;0W=55l-,?h!C=bY-t3b+G'kCL`h9T,)@@n#U5'IJdendstream
-endobj
-15 0 obj
-<<
-/BitsPerComponent 8 /ColorSpace /DeviceGray /Decode [ 0 1 ] /Filter [ /ASCII85Decode /FlateDecode ] /Height 252 /Length 47
- /Subtype /Image /Type /XObject /Width 208
->>
-stream
-Gb"0;JH,ZM!5fq/lendstream
-endobj
-16 0 obj
-<<
-/Contents 24 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
-/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /XObject <<
-/FormXob.6ecd21492e0a5715746f0be234b6869b 6 0 R /FormXob.749ae106c88606c23a90309bfd770feb 14 0 R /FormXob.c5e3a26a0646a389e1cbcea49bd86cd4 3 0 R
->>
->> /Rotate 0 /Trans <<
-
->>
- /Type /Page
->>
-endobj
-17 0 obj
-<<
-/Contents 25 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
-/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
->> /Rotate 0 /Trans <<
-
->>
- /Type /Page
->>
-endobj
-18 0 obj
-<<
-/PageMode /UseNone /Pages 20 0 R /Type /Catalog
->>
-endobj
-19 0 obj
-<<
-/Author (\(anonymous\)) /CreationDate (D:20251031191729+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20251031191729+00'00') /Producer (ReportLab PDF Library - www.reportlab.com)
- /Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
->>
-endobj
-20 0 obj
-<<
-/Count 5 /Kids [ 8 0 R 11 0 R 13 0 R 16 0 R 17 0 R ] /Type /Pages
->>
-endobj
-21 0 obj
-<<
-/Filter [ /ASCII85Decode /FlateDecode ] /Length 1491
->>
-stream
-Gatm;9lo;`&A@sBoT>qc1#J/(iA+/Ff#cKPD(5ct7a).]8:jCOq!l;7Thn8B+4eb427K)DN"Lq-5GYN`E82?h^&hJJTlqS:l,//ec!)cd,o[&FY1`\R,AEha%DT6I*+@^[o#@E]1R:f&bf41mgkX2,fqlTeqg>95!M5C([?j;DTQUC3fLf?eYQ[KPS=R%)`oh5REDbKn-F)8GqV-KTXnPVB$2Wit?ROJ4=D]bjRfqJ-a7a>Ybb:P#"$'hl_EZj8kENiF79OGInBA',a3&IVdoac4$HlAsR*J;Afh,Ojl-=;AH/jh<*"]VC+^9>](288QblkJt_I(_:OlLj68tD'[nXcUd\FUaQ%iQ,M!7&40d9C_h;)ClbMo#+0o:RXgE?.Y)X%(NG.68D%$V4W=JBh_spT[LKt31!m5KNIXQ1))`n*^A34[X;QZ+E5d<:4>-0!/3eLp"`4B\p]/AZKK%1kX&s0FgT!_R)bs?!hlM5&.T\rV8:nguotZ79-($\rLXLMU+k[^O0k9b4Aa9"bn)m7PlS\oAhdNeS_4lI+(TCT@iEHB`dYjW.:$7qbIh$D$DV4`4L>`h`gW5[n!H,;+kZ&5dO3(QSGg"Q(cRm=)FuZ!l;B66&q!,7^`LCPLVn;a^k6^Ho%2aOA["WM1p^T@F-]S=oU-c:"Fp\ZkF0sjVY!@B&j3YN0NE0a"`@H>m,+tV\I,3\qYOh`iBblh;jjK.84kjToN:]%eGFq4NaIPL!sF^M@31*K>J;m$)!IXH'+FD='fWJGtj(2s$.>s21EUUA^LlF_._=;*:Dj:aCP63pCf9b(.A53YU.l4dih]/%4Xh8J'.9>lh!;?eeCjZ;HRbtGOsUs(Ta;$$,tKo%YMN:jO)b0##W;:nL^e!A-HQe$N"@L?(&+-e`Yendstream
-endobj
-22 0 obj
-<<
-/Filter [ /ASCII85Decode /FlateDecode ] /Length 1470
->>
-stream
-GatU3>BALZ&:WeDm)tfjSK:k<8M6(YCs8K)&+UO"A)3RRMV3Uc/h/=rC+S\Uss$$\&pB\L);kPa2$_X*O5r=1Be@G%pXX&*2/mL[c^eZd$Gc^N&h0l`d^[D`GX3qJ-TKA].6j;jsJMO8Z1toOmPMpV0h>]2l+("T6iRR&!:Cu>Ko[s6?eY%lt%M/Md0VRqH-_L>jNS!cTL7uunbR,:l[SEm[Y#&UX!m=!-Ni0'Y>aGWSd,FDY.l\5hf>)f7_Qto57APRTYm69mK_TDTS_[bL>k`ZZ:QK4ZENHXeU+bfiQ!uh?c0]jn[XE0$CXoFpVNK:IE/$cLUqB]q=nKj"JPS0eA(6S3no>Q:4)"u56@&!d(br/0YSH$^B+#<'4<59s)gSr0Jd;XIfM86Foj%$*aW032F)I3"Y+mi&FNqL[fPh=HU='SNI\\bfEp=lZ?ICp]>h$`)HZD@T8J$Pb$=::CJr<*%2jBjms$FrY6"T)1).Ytf7Z2-nABj)eD"97ZE+-F5aG1^N#CTb$pjXNmgYWY?qJaAV2(\s*M3&EastlSsH<#!'9&"W\G'Jl<4L(n=9QCfaLJ0Ek,@-k3oXV\Z58Jt7L./Bb+glbu5_+IdO\dK\df\5:<.1"BlIMeR$0="U(Rq*,&:9nleI'M*)3jX(S8F^;+.cH=9t!kJroQ,45kD1@m(@!R=@kaOluGorOAG/&n"1jlJSF:iIEO\:KIq1XZp?r"]LCqj/f[\5r!Y1@TaBrQ(u"'s_CE+9?>C(VtT$.RYN>_W'5]WqQ@>p`<=;s!RNurN2G.)e9d#Q#aG88ujuL"ib*%Z=r[Ep].])2u6W1$-$;CHo\O8TL@X/po(#`j4[Y\D(R%ZK'1+6V=+)IsdJ.RBo.n'!%7rd=5&noMS*CX=_S*4>)qYos:)9tIfd_YAa[]]sk^^-J'#5o=aI,YE,GV_1HMk:,IS0H6PXc1O"J8%67`g=,#.`84'(=.,.gnArRWm5?8@$i.endstream
-endobj
-23 0 obj
-<<
-/Filter [ /ASCII85Decode /FlateDecode ] /Length 816
->>
-stream
-Gatm992F=s&AIV:Qm8^UfT+uS:+[1dM/iS?HJoLXC^A;g*:G*NIE,Yr?ltK).[!l&H$nM.^R9%KoPf<:ZiC=kllU6*!qIdZ)+_hu_dP`SR.+`a,m'?hPXDOHJ>Xln3&3:.2V!I9n7>K)D^h@[c5$^l\t/aKN*0rClp5!p7>I^/e)Fc_^hKnYCuDgADD_:(^\A$/*TZ)uLGO67nVC]eM-)bZRiNbY3da8Y^F+?_Rr6g&nIFPaOGYbPERZKY_+O2MK)W>DcQGo+S?OC^!Q=1?P&tL4.Z%O!RsAss]Nc-E7"cq.dW`.H2Z'"sMV6#eJjHnECq,Q.2E:*9%&BO#6fQ<7nEA$0L3M8cFd%A