@fin.cx/skr
Version:
SKR03 and SKR04 German accounting standards for double-entry bookkeeping
434 lines • 35.6 kB
JavaScript
import * as plugins from './plugins.js';
import { Account } from './skr.classes.account.js';
import { Transaction } from './skr.classes.transaction.js';
import { JournalEntry } from './skr.classes.journalentry.js';
import { SKR03_ACCOUNTS } from './skr03.data.js';
import { SKR04_ACCOUNTS } from './skr04.data.js';
// Module-level Maps for O(1) SKR standard lookups
const STANDARD_SKR_MAP = {
SKR03: new Map(SKR03_ACCOUNTS.map(a => [a.accountNumber, a])),
SKR04: new Map(SKR04_ACCOUNTS.map(a => [a.accountNumber, a])),
};
export class Ledger {
constructor(skrType) {
this.skrType = skrType;
this.logger = new plugins.smartlog.Smartlog({
logContext: {
company: 'fin.cx',
companyunit: 'skr',
containerName: 'Ledger',
environment: 'local',
runtime: 'node',
zone: 'local',
},
});
}
/**
* Post a transaction with validation
*/
async postTransaction(transactionData) {
this.logger.log('info', `Posting transaction: ${transactionData.description}`);
// Ensure SKR type matches
const fullTransactionData = {
...transactionData,
skrType: this.skrType,
};
// Validate accounts exist
await this.validateAccounts([
transactionData.debitAccount,
transactionData.creditAccount,
]);
// Create and post transaction
const transaction = await Transaction.createTransaction(fullTransactionData);
this.logger.log('info', `Transaction ${transaction.transactionNumber} posted successfully`);
return transaction;
}
/**
* Post a journal entry with validation
*/
async postJournalEntry(journalData) {
this.logger.log('info', `Posting journal entry: ${journalData.description}`);
// Ensure SKR type matches
const fullJournalData = {
...journalData,
skrType: this.skrType,
};
// Validate all accounts exist
const accountNumbers = journalData.lines.map((line) => line.accountNumber);
await this.validateAccounts(accountNumbers);
// Validate against SKR standard (warnings only by default)
await this.validateAccountsAgainstSKR(journalData.lines, {
strict: false, // Start with warnings only
warnOnNameMismatch: false // Names vary, don't spam logs
});
// Validate journal entry is balanced
this.validateJournalBalance(journalData.lines);
// Create and post journal entry
const journalEntry = await JournalEntry.createJournalEntry(fullJournalData);
await journalEntry.post();
this.logger.log('info', `Journal entry ${journalEntry.journalNumber} posted successfully`);
return journalEntry;
}
/**
* Validate that accounts exist and are active
*/
async validateAccounts(accountNumbers) {
const uniqueAccountNumbers = [...new Set(accountNumbers)];
for (const accountNumber of uniqueAccountNumbers) {
const account = await Account.getAccountByNumber(accountNumber, this.skrType);
if (!account) {
throw new Error(`Account ${accountNumber} not found for ${this.skrType}`);
}
if (!account.isActive) {
throw new Error(`Account ${accountNumber} is not active`);
}
}
}
/**
* Validate journal entry balance
*/
validateJournalBalance(lines) {
let totalDebits = 0;
let totalCredits = 0;
for (const line of lines) {
if (line.debit)
totalDebits += line.debit;
if (line.credit)
totalCredits += line.credit;
}
const difference = Math.abs(totalDebits - totalCredits);
if (difference >= 0.01) {
throw new Error(`Journal entry is not balanced. Debits: ${totalDebits}, Credits: ${totalCredits}`);
}
}
/**
* Validate accounts against SKR standard data
*/
async validateAccountsAgainstSKR(lines, options) {
const { strict = false, warnOnNameMismatch = false } = options || {};
const skrMap = STANDARD_SKR_MAP[this.skrType];
if (!skrMap) {
this.logger.log('warn', `No SKR standard map available for ${this.skrType}`);
return;
}
const uniqueAccountNumbers = [...new Set(lines.map(line => line.accountNumber))];
for (const accountNumber of uniqueAccountNumbers) {
const standardAccount = skrMap.get(accountNumber);
if (!standardAccount) {
// Special case: SKR04 class 8 is designated for custom accounts ("frei")
if (this.skrType === 'SKR04' && accountNumber.startsWith('8')) {
this.logger.log('debug', `Account ${accountNumber} is in SKR04 class 8 (custom accounts allowed)`);
continue;
}
const message = `Account ${accountNumber} is not a standard ${this.skrType} account`;
if (strict) {
throw new Error(message);
}
else {
this.logger.log('warn', message);
}
continue;
}
// Get actual account from database to compare
const dbAccount = await Account.getAccountByNumber(accountNumber, this.skrType);
if (!dbAccount) {
// Account doesn't exist in DB, will be caught by validateAccounts()
continue;
}
// Validate type and class match SKR standard
if (dbAccount.accountType !== standardAccount.accountType) {
const message = `Account ${accountNumber} type mismatch: expected '${standardAccount.accountType}', got '${dbAccount.accountType}'`;
if (strict) {
throw new Error(message);
}
else {
this.logger.log('warn', message);
}
}
if (dbAccount.accountClass !== standardAccount.accountClass) {
const message = `Account ${accountNumber} class mismatch: expected ${standardAccount.accountClass}, got ${dbAccount.accountClass}`;
if (strict) {
throw new Error(message);
}
else {
this.logger.log('warn', message);
}
}
// Warn on name mismatch (common and acceptable in practice)
if (warnOnNameMismatch && dbAccount.accountName !== standardAccount.accountName) {
this.logger.log('info', `Account ${accountNumber} name differs from SKR standard: '${dbAccount.accountName}' vs '${standardAccount.accountName}'`);
}
}
}
/**
* Reverse a transaction
*/
async reverseTransaction(transactionId) {
this.logger.log('info', `Reversing transaction: ${transactionId}`);
const transaction = await Transaction.getTransactionById(transactionId);
if (!transaction) {
throw new Error(`Transaction ${transactionId} not found`);
}
if (transaction.skrType !== this.skrType) {
throw new Error(`Transaction ${transactionId} belongs to different SKR type`);
}
const reversalTransaction = await transaction.reverseTransaction();
this.logger.log('info', `Transaction reversed: ${reversalTransaction.transactionNumber}`);
return reversalTransaction;
}
/**
* Reverse a journal entry
*/
async reverseJournalEntry(journalId) {
this.logger.log('info', `Reversing journal entry: ${journalId}`);
const journalEntry = await JournalEntry.getInstance({ id: journalId });
if (!journalEntry) {
throw new Error(`Journal entry ${journalId} not found`);
}
if (journalEntry.skrType !== this.skrType) {
throw new Error(`Journal entry ${journalId} belongs to different SKR type`);
}
const reversalEntry = await journalEntry.reverse();
this.logger.log('info', `Journal entry reversed: ${reversalEntry.journalNumber}`);
return reversalEntry;
}
/**
* Get account history (all transactions for an account)
*/
async getAccountHistory(accountNumber, dateFrom, dateTo) {
const account = await Account.getAccountByNumber(accountNumber, this.skrType);
if (!account) {
throw new Error(`Account ${accountNumber} not found`);
}
let transactions = await Transaction.getTransactionsByAccount(accountNumber, this.skrType);
// Apply date filter if provided
if (dateFrom || dateTo) {
transactions = transactions.filter((transaction) => {
if (dateFrom && transaction.date < dateFrom)
return false;
if (dateTo && transaction.date > dateTo)
return false;
return true;
});
}
// Sort by date
transactions.sort((a, b) => a.date.getTime() - b.date.getTime());
return transactions;
}
/**
* Get account balance at a specific date
*/
async getAccountBalance(accountNumber, asOfDate) {
const account = await Account.getAccountByNumber(accountNumber, this.skrType);
if (!account) {
throw new Error(`Account ${accountNumber} not found`);
}
let transactions = await Transaction.getTransactionsByAccount(accountNumber, this.skrType);
// Filter transactions up to the specified date
if (asOfDate) {
transactions = transactions.filter((t) => t.date <= asOfDate);
}
// Calculate balance
let debitTotal = 0;
let creditTotal = 0;
for (const transaction of transactions) {
if (transaction.debitAccount === accountNumber) {
debitTotal += transaction.amount;
}
if (transaction.creditAccount === accountNumber) {
creditTotal += transaction.amount;
}
}
// Calculate net balance based on account type
let balance;
switch (account.accountType) {
case 'asset':
case 'expense':
// Normal debit accounts
balance = debitTotal - creditTotal;
break;
case 'liability':
case 'equity':
case 'revenue':
// Normal credit accounts
balance = creditTotal - debitTotal;
break;
}
return {
accountNumber,
debitTotal,
creditTotal,
balance,
lastUpdated: new Date(),
};
}
/**
* Close accounting period (create closing entries)
*/
async closeAccountingPeriod(period, // Format: YYYY-MM
closingAccountNumber = '9400') {
this.logger.log('info', `Closing accounting period: ${period}`);
const closingEntries = [];
// Get all revenue and expense accounts
const revenueAccounts = await Account.getAccountsByType('revenue', this.skrType);
const expenseAccounts = await Account.getAccountsByType('expense', this.skrType);
// Calculate totals for each account in the period
const periodTransactions = await Transaction.getTransactionsByPeriod(period, this.skrType);
// Create closing entry for revenue accounts
const revenueLines = [];
let totalRevenue = 0;
for (const account of revenueAccounts) {
const balance = await this.getAccountBalanceForPeriod(account.accountNumber, periodTransactions);
if (balance !== 0) {
// Revenue accounts have credit balance, so debit to close
revenueLines.push({
accountNumber: account.accountNumber,
debit: Math.abs(balance),
description: `Closing ${account.accountName}`,
});
totalRevenue += Math.abs(balance);
}
}
if (totalRevenue > 0) {
// Credit the closing account
revenueLines.push({
accountNumber: closingAccountNumber,
credit: totalRevenue,
description: 'Revenue closing to P&L',
});
const revenueClosingEntry = await this.postJournalEntry({
date: new Date(),
description: `Closing revenue accounts for period ${period}`,
reference: `CLOSE-REV-${period}`,
lines: revenueLines,
skrType: this.skrType,
});
closingEntries.push(revenueClosingEntry);
}
// Create closing entry for expense accounts
const expenseLines = [];
let totalExpense = 0;
for (const account of expenseAccounts) {
const balance = await this.getAccountBalanceForPeriod(account.accountNumber, periodTransactions);
if (balance !== 0) {
// Expense accounts have debit balance, so credit to close
expenseLines.push({
accountNumber: account.accountNumber,
credit: Math.abs(balance),
description: `Closing ${account.accountName}`,
});
totalExpense += Math.abs(balance);
}
}
if (totalExpense > 0) {
// Debit the closing account
expenseLines.push({
accountNumber: closingAccountNumber,
debit: totalExpense,
description: 'Expense closing to P&L',
});
const expenseClosingEntry = await this.postJournalEntry({
date: new Date(),
description: `Closing expense accounts for period ${period}`,
reference: `CLOSE-EXP-${period}`,
lines: expenseLines,
skrType: this.skrType,
});
closingEntries.push(expenseClosingEntry);
}
this.logger.log('info', `Period ${period} closed with ${closingEntries.length} entries`);
return closingEntries;
}
/**
* Calculate account balance for a specific set of transactions
*/
async getAccountBalanceForPeriod(accountNumber, transactions) {
const account = await Account.getAccountByNumber(accountNumber, this.skrType);
if (!account)
return 0;
let debitTotal = 0;
let creditTotal = 0;
for (const transaction of transactions) {
if (transaction.debitAccount === accountNumber) {
debitTotal += transaction.amount;
}
if (transaction.creditAccount === accountNumber) {
creditTotal += transaction.amount;
}
}
// Calculate net balance based on account type
switch (account.accountType) {
case 'asset':
case 'expense':
return debitTotal - creditTotal;
case 'liability':
case 'equity':
case 'revenue':
return creditTotal - debitTotal;
}
}
/**
* Validate double-entry rules
*/
validateDoubleEntry(debitAmount, creditAmount) {
return Math.abs(debitAmount - creditAmount) < 0.01;
}
/**
* Get unbalanced transactions (for audit)
*/
async getUnbalancedTransactions() {
// In a proper double-entry system, all posted transactions should be balanced
// This method is mainly for audit purposes
const allTransactions = await Transaction.getInstances({
skrType: this.skrType,
status: 'posted',
});
// Group transactions by journal entry if they have one
const unbalanced = [];
// Since our system ensures balance at posting time,
// this should typically return an empty array
// But we include it for completeness and audit purposes
return unbalanced;
}
/**
* Recalculate all account balances
*/
async recalculateAllBalances() {
this.logger.log('info', 'Recalculating all account balances');
// Get all accounts
const accounts = await Account.getInstances({ skrType: this.skrType });
for (const account of accounts) {
// Reset balances
account.debitTotal = 0;
account.creditTotal = 0;
account.balance = 0;
// Get all transactions for this account
const transactions = await Transaction.getTransactionsByAccount(account.accountNumber, this.skrType);
// Recalculate totals
for (const transaction of transactions) {
if (transaction.debitAccount === account.accountNumber) {
account.debitTotal += transaction.amount;
}
if (transaction.creditAccount === account.accountNumber) {
account.creditTotal += transaction.amount;
}
}
// Calculate balance based on account type
switch (account.accountType) {
case 'asset':
case 'expense':
account.balance = account.debitTotal - account.creditTotal;
break;
case 'liability':
case 'equity':
case 'revenue':
account.balance = account.creditTotal - account.debitTotal;
break;
}
account.updatedAt = new Date();
await account.save();
}
this.logger.log('info', `Recalculated balances for ${accounts.length} accounts`);
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2tyLmNsYXNzZXMubGVkZ2VyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvc2tyLmNsYXNzZXMubGVkZ2VyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBQ3hDLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUNuRCxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFDM0QsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBUTdELE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUNqRCxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFFakQsa0RBQWtEO0FBQ2xELE1BQU0sZ0JBQWdCLEdBQUc7SUFDdkIsS0FBSyxFQUFFLElBQUksR0FBRyxDQUFDLGNBQWMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxhQUFhLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUM3RCxLQUFLLEVBQUUsSUFBSSxHQUFHLENBQUMsY0FBYyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLGFBQWEsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDO0NBQzlELENBQUM7QUFFRixNQUFNLE9BQU8sTUFBTTtJQUdqQixZQUFvQixPQUFpQjtRQUFqQixZQUFPLEdBQVAsT0FBTyxDQUFVO1FBQ25DLElBQUksQ0FBQyxNQUFNLEdBQUcsSUFBSSxPQUFPLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBQztZQUMxQyxVQUFVLEVBQUU7Z0JBQ1YsT0FBTyxFQUFFLFFBQVE7Z0JBQ2pCLFdBQVcsRUFBRSxLQUFLO2dCQUNsQixhQUFhLEVBQUUsUUFBUTtnQkFDdkIsV0FBVyxFQUFFLE9BQU87Z0JBQ3BCLE9BQU8sRUFBRSxNQUFNO2dCQUNmLElBQUksRUFBRSxPQUFPO2FBQ2Q7U0FDRixDQUFDLENBQUM7SUFDTCxDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsZUFBZSxDQUMxQixlQUFpQztRQUVqQyxJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FDYixNQUFNLEVBQ04sd0JBQXdCLGVBQWUsQ0FBQyxXQUFXLEVBQUUsQ0FDdEQsQ0FBQztRQUVGLDBCQUEwQjtRQUMxQixNQUFNLG1CQUFtQixHQUFxQjtZQUM1QyxHQUFHLGVBQWU7WUFDbEIsT0FBTyxFQUFFLElBQUksQ0FBQyxPQUFPO1NBQ3RCLENBQUM7UUFFRiwwQkFBMEI7UUFDMUIsTUFBTSxJQUFJLENBQUMsZ0JBQWdCLENBQUM7WUFDMUIsZUFBZSxDQUFDLFlBQVk7WUFDNUIsZUFBZSxDQUFDLGFBQWE7U0FDOUIsQ0FBQyxDQUFDO1FBRUgsOEJBQThCO1FBQzlCLE1BQU0sV0FBVyxHQUNmLE1BQU0sV0FBVyxDQUFDLGlCQUFpQixDQUFDLG1CQUFtQixDQUFDLENBQUM7UUFFM0QsSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQ2IsTUFBTSxFQUNOLGVBQWUsV0FBVyxDQUFDLGlCQUFpQixzQkFBc0IsQ0FDbkUsQ0FBQztRQUNGLE9BQU8sV0FBVyxDQUFDO0lBQ3JCLENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQyxnQkFBZ0IsQ0FDM0IsV0FBMEI7UUFFMUIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQ2IsTUFBTSxFQUNOLDBCQUEwQixXQUFXLENBQUMsV0FBVyxFQUFFLENBQ3BELENBQUM7UUFFRiwwQkFBMEI7UUFDMUIsTUFBTSxlQUFlLEdBQWtCO1lBQ3JDLEdBQUcsV0FBVztZQUNkLE9BQU8sRUFBRSxJQUFJLENBQUMsT0FBTztTQUN0QixDQUFDO1FBRUYsOEJBQThCO1FBQzlCLE1BQU0sY0FBYyxHQUFHLFdBQVcsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsSUFBSSxFQUFFLEVBQUUsQ0FBQyxJQUFJLENBQUMsYUFBYSxDQUFDLENBQUM7UUFDM0UsTUFBTSxJQUFJLENBQUMsZ0JBQWdCLENBQUMsY0FBYyxDQUFDLENBQUM7UUFFNUMsMkRBQTJEO1FBQzNELE1BQU0sSUFBSSxDQUFDLDBCQUEwQixDQUFDLFdBQVcsQ0FBQyxLQUFLLEVBQUU7WUFDdkQsTUFBTSxFQUFFLEtBQUssRUFBRywyQkFBMkI7WUFDM0Msa0JBQWtCLEVBQUUsS0FBSyxDQUFFLDhCQUE4QjtTQUMxRCxDQUFDLENBQUM7UUFFSCxxQ0FBcUM7UUFDckMsSUFBSSxDQUFDLHNCQUFzQixDQUFDLFdBQVcsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUUvQyxnQ0FBZ0M7UUFDaEMsTUFBTSxZQUFZLEdBQUcsTUFBTSxZQUFZLENBQUMsa0JBQWtCLENBQUMsZUFBZSxDQUFDLENBQUM7UUFDNUUsTUFBTSxZQUFZLENBQUMsSUFBSSxFQUFFLENBQUM7UUFFMUIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQ2IsTUFBTSxFQUNOLGlCQUFpQixZQUFZLENBQUMsYUFBYSxzQkFBc0IsQ0FDbEUsQ0FBQztRQUNGLE9BQU8sWUFBWSxDQUFDO0lBQ3RCLENBQUM7SUFFRDs7T0FFRztJQUNLLEtBQUssQ0FBQyxnQkFBZ0IsQ0FBQyxjQUF3QjtRQUNyRCxNQUFNLG9CQUFvQixHQUFHLENBQUMsR0FBRyxJQUFJLEdBQUcsQ0FBQyxjQUFjLENBQUMsQ0FBQyxDQUFDO1FBRTFELEtBQUssTUFBTSxhQUFhLElBQUksb0JBQW9CLEVBQUUsQ0FBQztZQUNqRCxNQUFNLE9BQU8sR0FBRyxNQUFNLE9BQU8sQ0FBQyxrQkFBa0IsQ0FDOUMsYUFBYSxFQUNiLElBQUksQ0FBQyxPQUFPLENBQ2IsQ0FBQztZQUVGLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQztnQkFDYixNQUFNLElBQUksS0FBSyxDQUNiLFdBQVcsYUFBYSxrQkFBa0IsSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUN6RCxDQUFDO1lBQ0osQ0FBQztZQUVELElBQUksQ0FBQyxPQUFPLENBQUMsUUFBUSxFQUFFLENBQUM7Z0JBQ3RCLE1BQU0sSUFBSSxLQUFLLENBQUMsV0FBVyxhQUFhLGdCQUFnQixDQUFDLENBQUM7WUFDNUQsQ0FBQztRQUNILENBQUM7SUFDSCxDQUFDO0lBRUQ7O09BRUc7SUFDSyxzQkFBc0IsQ0FBQyxLQUEwQjtRQUN2RCxJQUFJLFdBQVcsR0FBRyxDQUFDLENBQUM7UUFDcEIsSUFBSSxZQUFZLEdBQUcsQ0FBQyxDQUFDO1FBRXJCLEtBQUssTUFBTSxJQUFJLElBQUksS0FBSyxFQUFFLENBQUM7WUFDekIsSUFBSSxJQUFJLENBQUMsS0FBSztnQkFBRSxXQUFXLElBQUksSUFBSSxDQUFDLEtBQUssQ0FBQztZQUMxQyxJQUFJLElBQUksQ0FBQyxNQUFNO2dCQUFFLFlBQVksSUFBSSxJQUFJLENBQUMsTUFBTSxDQUFDO1FBQy9DLENBQUM7UUFFRCxNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLFdBQVcsR0FBRyxZQUFZLENBQUMsQ0FBQztRQUN4RCxJQUFJLFVBQVUsSUFBSSxJQUFJLEVBQUUsQ0FBQztZQUN2QixNQUFNLElBQUksS0FBSyxDQUNiLDBDQUEwQyxXQUFXLGNBQWMsWUFBWSxFQUFFLENBQ2xGLENBQUM7UUFDSixDQUFDO0lBQ0gsQ0FBQztJQUVEOztPQUVHO0lBQ0ssS0FBSyxDQUFDLDBCQUEwQixDQUN0QyxLQUEwQixFQUMxQixPQUE0RDtRQUU1RCxNQUFNLEVBQUUsTUFBTSxHQUFHLEtBQUssRUFBRSxrQkFBa0IsR0FBRyxLQUFLLEVBQUUsR0FBRyxPQUFPLElBQUksRUFBRSxDQUFDO1FBQ3JFLE1BQU0sTUFBTSxHQUFHLGdCQUFnQixDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUU5QyxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDWixJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxNQUFNLEVBQUUscUNBQXFDLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFDO1lBQzdFLE9BQU87UUFDVCxDQUFDO1FBRUQsTUFBTSxvQkFBb0IsR0FBRyxDQUFDLEdBQUcsSUFBSSxHQUFHLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxhQUFhLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFFakYsS0FBSyxNQUFNLGFBQWEsSUFBSSxvQkFBb0IsRUFBRSxDQUFDO1lBQ2pELE1BQU0sZUFBZSxHQUFHLE1BQU0sQ0FBQyxHQUFHLENBQUMsYUFBYSxDQUFDLENBQUM7WUFFbEQsSUFBSSxDQUFDLGVBQWUsRUFBRSxDQUFDO2dCQUNyQix5RUFBeUU7Z0JBQ3pFLElBQUksSUFBSSxDQUFDLE9BQU8sS0FBSyxPQUFPLElBQUksYUFBYSxDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDO29CQUM5RCxJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxPQUFPLEVBQUUsV0FBVyxhQUFhLGdEQUFnRCxDQUFDLENBQUM7b0JBQ25HLFNBQVM7Z0JBQ1gsQ0FBQztnQkFFRCxNQUFNLE9BQU8sR0FBRyxXQUFXLGFBQWEsc0JBQXNCLElBQUksQ0FBQyxPQUFPLFVBQVUsQ0FBQztnQkFDckYsSUFBSSxNQUFNLEVBQUUsQ0FBQztvQkFDWCxNQUFNLElBQUksS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDO2dCQUMzQixDQUFDO3FCQUFNLENBQUM7b0JBQ04sSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsTUFBTSxFQUFFLE9BQU8sQ0FBQyxDQUFDO2dCQUNuQyxDQUFDO2dCQUNELFNBQVM7WUFDWCxDQUFDO1lBRUQsOENBQThDO1lBQzlDLE1BQU0sU0FBUyxHQUFHLE1BQU0sT0FBTyxDQUFDLGtCQUFrQixDQUFDLGFBQWEsRUFBRSxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUM7WUFDaEYsSUFBSSxDQUFDLFNBQVMsRUFBRSxDQUFDO2dCQUNmLG9FQUFvRTtnQkFDcEUsU0FBUztZQUNYLENBQUM7WUFFRCw2Q0FBNkM7WUFDN0MsSUFBSSxTQUFTLENBQUMsV0FBVyxLQUFLLGVBQWUsQ0FBQyxXQUFXLEVBQUUsQ0FBQztnQkFDMUQsTUFBTSxPQUFPLEdBQUcsV0FBVyxhQUFhLDZCQUE2QixlQUFlLENBQUMsV0FBVyxXQUFXLFNBQVMsQ0FBQyxXQUFXLEdBQUcsQ0FBQztnQkFDcEksSUFBSSxNQUFNLEVBQUUsQ0FBQztvQkFDWCxNQUFNLElBQUksS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDO2dCQUMzQixDQUFDO3FCQUFNLENBQUM7b0JBQ04sSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsTUFBTSxFQUFFLE9BQU8sQ0FBQyxDQUFDO2dCQUNuQyxDQUFDO1lBQ0gsQ0FBQztZQUVELElBQUksU0FBUyxDQUFDLFlBQVksS0FBSyxlQUFlLENBQUMsWUFBWSxFQUFFLENBQUM7Z0JBQzVELE1BQU0sT0FBTyxHQUFHLFdBQVcsYUFBYSw2QkFBNkIsZUFBZSxDQUFDLFlBQVksU0FBUyxTQUFTLENBQUMsWUFBWSxFQUFFLENBQUM7Z0JBQ25JLElBQUksTUFBTSxFQUFFLENBQUM7b0JBQ1gsTUFBTSxJQUFJLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQztnQkFDM0IsQ0FBQztxQkFBTSxDQUFDO29CQUNOLElBQUksQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLE1BQU0sRUFBRSxPQUFPLENBQUMsQ0FBQztnQkFDbkMsQ0FBQztZQUNILENBQUM7WUFFRCw0REFBNEQ7WUFDNUQsSUFBSSxrQkFBa0IsSUFBSSxTQUFTLENBQUMsV0FBVyxLQUFLLGVBQWUsQ0FBQyxXQUFXLEVBQUUsQ0FBQztnQkFDaEYsSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsTUFBTSxFQUNwQixXQUFXLGFBQWEscUNBQXFDLFNBQVMsQ0FBQyxXQUFXLFNBQVMsZUFBZSxDQUFDLFdBQVcsR0FBRyxDQUMxSCxDQUFDO1lBQ0osQ0FBQztRQUNILENBQUM7SUFDSCxDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsa0JBQWtCLENBQUMsYUFBcUI7UUFDbkQsSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsTUFBTSxFQUFFLDBCQUEwQixhQUFhLEVBQUUsQ0FBQyxDQUFDO1FBRW5FLE1BQU0sV0FBVyxHQUFHLE1BQU0sV0FBVyxDQUFDLGtCQUFrQixDQUFDLGFBQWEsQ0FBQyxDQUFDO1FBQ3hFLElBQUksQ0FBQyxXQUFXLEVBQUUsQ0FBQztZQUNqQixNQUFNLElBQUksS0FBSyxDQUFDLGVBQWUsYUFBYSxZQUFZLENBQUMsQ0FBQztRQUM1RCxDQUFDO1FBRUQsSUFBSSxXQUFXLENBQUMsT0FBTyxLQUFLLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQztZQUN6QyxNQUFNLElBQUksS0FBSyxDQUNiLGVBQWUsYUFBYSxnQ0FBZ0MsQ0FDN0QsQ0FBQztRQUNKLENBQUM7UUFFRCxNQUFNLG1CQUFtQixHQUFHLE1BQU0sV0FBVyxDQUFDLGtCQUFrQixFQUFFLENBQUM7UUFFbkUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQ2IsTUFBTSxFQUNOLHlCQUF5QixtQkFBbUIsQ0FBQyxpQkFBaUIsRUFBRSxDQUNqRSxDQUFDO1FBQ0YsT0FBTyxtQkFBbUIsQ0FBQztJQUM3QixDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsbUJBQW1CLENBQUMsU0FBaUI7UUFDaEQsSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsTUFBTSxFQUFFLDRCQUE0QixTQUFTLEVBQUUsQ0FBQyxDQUFDO1FBRWpFLE1BQU0sWUFBWSxHQUFHLE1BQU0sWUFBWSxDQUFDLFdBQVcsQ0FBQyxFQUFFLEVBQUUsRUFBRSxTQUFTLEVBQUUsQ0FBQyxDQUFDO1FBQ3ZFLElBQUksQ0FBQyxZQUFZLEVBQUUsQ0FBQztZQUNsQixNQUFNLElBQUksS0FBSyxDQUFDLGlCQUFpQixTQUFTLFlBQVksQ0FBQyxDQUFDO1FBQzFELENBQUM7UUFFRCxJQUFJLFlBQVksQ0FBQyxPQUFPLEtBQUssSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDO1lBQzFDLE1BQU0sSUFBSSxLQUFLLENBQ2IsaUJBQWlCLFNBQVMsZ0NBQWdDLENBQzNELENBQUM7UUFDSixDQUFDO1FBRUQsTUFBTSxhQUFhLEdBQUcsTUFBTSxZQUFZLENBQUMsT0FBTyxFQUFFLENBQUM7UUFFbkQsSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQ2IsTUFBTSxFQUNOLDJCQUEyQixhQUFhLENBQUMsYUFBYSxFQUFFLENBQ3pELENBQUM7UUFDRixPQUFPLGFBQWEsQ0FBQztJQUN2QixDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsaUJBQWlCLENBQzVCLGFBQXFCLEVBQ3JCLFFBQWUsRUFDZixNQUFhO1FBRWIsTUFBTSxPQUFPLEdBQUcsTUFBTSxPQUFPLENBQUMsa0JBQWtCLENBQzlDLGFBQWEsRUFDYixJQUFJLENBQUMsT0FBTyxDQUNiLENBQUM7UUFDRixJQUFJLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDYixNQUFNLElBQUksS0FBSyxDQUFDLFdBQVcsYUFBYSxZQUFZLENBQUMsQ0FBQztRQUN4RCxDQUFDO1FBRUQsSUFBSSxZQUFZLEdBQUcsTUFBTSxXQUFXLENBQUMsd0JBQXdCLENBQzNELGFBQWEsRUFDYixJQUFJLENBQUMsT0FBTyxDQUNiLENBQUM7UUFFRixnQ0FBZ0M7UUFDaEMsSUFBSSxRQUFRLElBQUksTUFBTSxFQUFFLENBQUM7WUFDdkIsWUFBWSxHQUFHLFlBQVksQ0FBQyxNQUFNLENBQUMsQ0FBQyxXQUFXLEVBQUUsRUFBRTtnQkFDakQsSUFBSSxRQUFRLElBQUksV0FBVyxDQUFDLElBQUksR0FBRyxRQUFRO29CQUFFLE9BQU8sS0FBSyxDQUFDO2dCQUMxRCxJQUFJLE1BQU0sSUFBSSxXQUFXLENBQUMsSUFBSSxHQUFHLE1BQU07b0JBQUUsT0FBTyxLQUFLLENBQUM7Z0JBQ3RELE9BQU8sSUFBSSxDQUFDO1lBQ2QsQ0FBQyxDQUFDLENBQUM7UUFDTCxDQUFDO1FBRUQsZUFBZTtRQUNmLFlBQVksQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxHQUFHLENBQUMsQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQztRQUVqRSxPQUFPLFlBQVksQ0FBQztJQUN0QixDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsaUJBQWlCLENBQzVCLGFBQXFCLEVBQ3JCLFFBQWU7UUFFZixNQUFNLE9BQU8sR0FBRyxNQUFNLE9BQU8sQ0FBQyxrQkFBa0IsQ0FDOUMsYUFBYSxFQUNiLElBQUksQ0FBQyxPQUFPLENBQ2IsQ0FBQztRQUNGLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQztZQUNiLE1BQU0sSUFBSSxLQUFLLENBQUMsV0FBVyxhQUFhLFlBQVksQ0FBQyxDQUFDO1FBQ3hELENBQUM7UUFFRCxJQUFJLFlBQVksR0FBRyxNQUFNLFdBQVcsQ0FBQyx3QkFBd0IsQ0FDM0QsYUFBYSxFQUNiLElBQUksQ0FBQyxPQUFPLENBQ2IsQ0FBQztRQUVGLCtDQUErQztRQUMvQyxJQUFJLFFBQVEsRUFBRSxDQUFDO1lBQ2IsWUFBWSxHQUFHLFlBQVksQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxJQUFJLElBQUksUUFBUSxDQUFDLENBQUM7UUFDaEUsQ0FBQztRQUVELG9CQUFvQjtRQUNwQixJQUFJLFVBQVUsR0FBRyxDQUFDLENBQUM7UUFDbkIsSUFBSSxXQUFXLEdBQUcsQ0FBQyxDQUFDO1FBRXBCLEtBQUssTUFBTSxXQUFXLElBQUksWUFBWSxFQUFFLENBQUM7WUFDdkMsSUFBSSxXQUFXLENBQUMsWUFBWSxLQUFLLGFBQWEsRUFBRSxDQUFDO2dCQUMvQyxVQUFVLElBQUksV0FBVyxDQUFDLE1BQU0sQ0FBQztZQUNuQyxDQUFDO1lBQ0QsSUFBSSxXQUFXLENBQUMsYUFBYSxLQUFLLGFBQWEsRUFBRSxDQUFDO2dCQUNoRCxXQUFXLElBQUksV0FBVyxDQUFDLE1BQU0sQ0FBQztZQUNwQyxDQUFDO1FBQ0gsQ0FBQztRQUVELDhDQUE4QztRQUM5QyxJQUFJLE9BQWUsQ0FBQztRQUNwQixRQUFRLE9BQU8sQ0FBQyxXQUFXLEVBQUUsQ0FBQztZQUM1QixLQUFLLE9BQU8sQ0FBQztZQUNiLEtBQUssU0FBUztnQkFDWix3QkFBd0I7Z0JBQ3hCLE9BQU8sR0FBRyxVQUFVLEdBQUcsV0FBVyxDQUFDO2dCQUNuQyxNQUFNO1lBQ1IsS0FBSyxXQUFXLENBQUM7WUFDakIsS0FBSyxRQUFRLENBQUM7WUFDZCxLQUFLLFNBQVM7Z0JBQ1oseUJBQXlCO2dCQUN6QixPQUFPLEdBQUcsV0FBVyxHQUFHLFVBQVUsQ0FBQztnQkFDbkMsTUFBTTtRQUNWLENBQUM7UUFFRCxPQUFPO1lBQ0wsYUFBYTtZQUNiLFVBQVU7WUFDVixXQUFXO1lBQ1gsT0FBTztZQUNQLFdBQVcsRUFBRSxJQUFJLElBQUksRUFBRTtTQUN4QixDQUFDO0lBQ0osQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLHFCQUFxQixDQUNoQyxNQUFjLEVBQUUsa0JBQWtCO0lBQ2xDLHVCQUErQixNQUFNO1FBRXJDLElBQUksQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLE1BQU0sRUFBRSw4QkFBOEIsTUFBTSxFQUFFLENBQUMsQ0FBQztRQUVoRSxNQUFNLGNBQWMsR0FBbUIsRUFBRSxDQUFDO1FBRTFDLHVDQUF1QztRQUN2QyxNQUFNLGVBQWUsR0FBRyxNQUFNLE9BQU8sQ0FBQyxpQkFBaUIsQ0FDckQsU0FBUyxFQUNULElBQUksQ0FBQyxPQUFPLENBQ2IsQ0FBQztRQUNGLE1BQU0sZUFBZSxHQUFHLE1BQU0sT0FBTyxDQUFDLGlCQUFpQixDQUNyRCxTQUFTLEVBQ1QsSUFBSSxDQUFDLE9BQU8sQ0FDYixDQUFDO1FBRUYsa0RBQWtEO1FBQ2xELE1BQU0sa0JBQWtCLEdBQUcsTUFBTSxXQUFXLENBQUMsdUJBQXVCLENBQ2xFLE1BQU0sRUFDTixJQUFJLENBQUMsT0FBTyxDQUNiLENBQUM7UUFFRiw0Q0FBNEM7UUFDNUMsTUFBTSxZQUFZLEdBQXdCLEVBQUUsQ0FBQztRQUM3QyxJQUFJLFlBQVksR0FBRyxDQUFDLENBQUM7UUFFckIsS0FBSyxNQUFNLE9BQU8sSUFBSSxlQUFlLEVBQUUsQ0FBQztZQUN0QyxNQUFNLE9BQU8sR0FBRyxNQUFNLElBQUksQ0FBQywwQkFBMEIsQ0FDbkQsT0FBTyxDQUFDLGFBQWEsRUFDckIsa0JBQWtCLENBQ25CLENBQUM7WUFFRixJQUFJLE9BQU8sS0FBSyxDQUFDLEVBQUUsQ0FBQztnQkFDbEIsMERBQTBEO2dCQUMxRCxZQUFZLENBQUMsSUFBSSxDQUFDO29CQUNoQixhQUFhLEVBQUUsT0FBTyxDQUFDLGFBQWE7b0JBQ3BDLEtBQUssRUFBRSxJQUFJLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQztvQkFDeEIsV0FBVyxFQUFFLFdBQVcsT0FBTyxDQUFDLFdBQVcsRUFBRTtpQkFDOUMsQ0FBQyxDQUFDO2dCQUNILFlBQVksSUFBSSxJQUFJLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1lBQ3BDLENBQUM7UUFDSCxDQUFDO1FBRUQsSUFBSSxZQUFZLEdBQUcsQ0FBQyxFQUFFLENBQUM7WUFDckIsNkJBQTZCO1lBQzdCLFlBQVksQ0FBQyxJQUFJLENBQUM7Z0JBQ2hCLGFBQWEsRUFBRSxvQkFBb0I7Z0JBQ25DLE1BQU0sRUFBRSxZQUFZO2dCQUNwQixXQUFXLEVBQUUsd0JBQXdCO2FBQ3RDLENBQUMsQ0FBQztZQUVILE1BQU0sbUJBQW1CLEdBQUcsTUFBTSxJQUFJLENBQUMsZ0JBQWdCLENBQUM7Z0JBQ3RELElBQUksRUFBRSxJQUFJLElBQUksRUFBRTtnQkFDaEIsV0FBVyxFQUFFLHVDQUF1QyxNQUFNLEVBQUU7Z0JBQzVELFNBQVMsRUFBRSxhQUFhLE1BQU0sRUFBRTtnQkFDaEMsS0FBSyxFQUFFLFlBQVk7Z0JBQ25CLE9BQU8sRUFBRSxJQUFJLENBQUMsT0FBTzthQUN0QixDQUFDLENBQUM7WUFFSCxjQUFjLENBQUMsSUFBSSxDQUFDLG1CQUFtQixDQUFDLENBQUM7UUFDM0MsQ0FBQztRQUVELDRDQUE0QztRQUM1QyxNQUFNLFlBQVksR0FBd0IsRUFBRSxDQUFDO1FBQzdDLElBQUksWUFBWSxHQUFHLENBQUMsQ0FBQztRQUVyQixLQUFLLE1BQU0sT0FBTyxJQUFJLGVBQWUsRUFBRSxDQUFDO1lBQ3RDLE1BQU0sT0FBTyxHQUFHLE1BQU0sSUFBSSxDQUFDLDBCQUEwQixDQUNuRCxPQUFPLENBQUMsYUFBYSxFQUNyQixrQkFBa0IsQ0FDbkIsQ0FBQztZQUVGLElBQUksT0FBTyxLQUFLLENBQUMsRUFBRSxDQUFDO2dCQUNsQiwwREFBMEQ7Z0JBQzFELFlBQVksQ0FBQyxJQUFJLENBQUM7b0JBQ2hCLGFBQWEsRUFBRSxPQUFPLENBQUMsYUFBYTtvQkFDcEMsTUFBTSxFQUFFLElBQUksQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDO29CQUN6QixXQUFXLEVBQUUsV0FBVyxPQUFPLENBQUMsV0FBVyxFQUFFO2lCQUM5QyxDQUFDLENBQUM7Z0JBQ0gsWUFBWSxJQUFJLElBQUksQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLENBQUM7WUFDcEMsQ0FBQztRQUNILENBQUM7UUFFRCxJQUFJLFlBQVksR0FBRyxDQUFDLEVBQUUsQ0FBQztZQUNyQiw0QkFBNEI7WUFDNUIsWUFBWSxDQUFDLElBQUksQ0FBQztnQkFDaEIsYUFBYSxFQUFFLG9CQUFvQjtnQkFDbkMsS0FBSyxFQUFFLFlBQVk7Z0JBQ25CLFdBQVcsRUFBRSx3QkFBd0I7YUFDdEMsQ0FBQyxDQUFDO1lBRUgsTUFBTSxtQkFBbUIsR0FBRyxNQUFNLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQztnQkFDdEQsSUFBSSxFQUFFLElBQUksSUFBSSxFQUFFO2dCQUNoQixXQUFXLEVBQUUsdUNBQXVDLE1BQU0sRUFBRTtnQkFDNUQsU0FBUyxFQUFFLGFBQWEsTUFBTSxFQUFFO2dCQUNoQyxLQUFLLEVBQUUsWUFBWTtnQkFDbkIsT0FBTyxFQUFFLElBQUksQ0FBQyxPQUFPO2FBQ3RCLENBQUMsQ0FBQztZQUVILGNBQWMsQ0FBQyxJQUFJLENBQUMsbUJBQW1CLENBQUMsQ0FBQztRQUMzQyxDQUFDO1FBRUQsSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQ2IsTUFBTSxFQUNOLFVBQVUsTUFBTSxnQkFBZ0IsY0FBYyxDQUFDLE1BQU0sVUFBVSxDQUNoRSxDQUFDO1FBQ0YsT0FBTyxjQUFjLENBQUM7SUFDeEIsQ0FBQztJQUVEOztPQUVHO0lBQ0ssS0FBSyxDQUFDLDBCQUEwQixDQUN0QyxhQUFxQixFQUNyQixZQUEyQjtRQUUzQixNQUFNLE9BQU8sR0FBRyxNQUFNLE9BQU8sQ0FBQyxrQkFBa0IsQ0FDOUMsYUFBYSxFQUNiLElBQUksQ0FBQyxPQUFPLENBQ2IsQ0FBQztRQUNGLElBQUksQ0FBQyxPQUFPO1lBQUUsT0FBTyxDQUFDLENBQUM7UUFFdkIsSUFBSSxVQUFVLEdBQUcsQ0FBQyxDQUFDO1FBQ25CLElBQUksV0FBVyxHQUFHLENBQUMsQ0FBQztRQUVwQixLQUFLLE1BQU0sV0FBVyxJQUFJLFlBQVksRUFBRSxDQUFDO1lBQ3ZDLElBQUksV0FBVyxDQUFDLFlBQVksS0FBSyxhQUFhLEVBQUUsQ0FBQztnQkFDL0MsVUFBVSxJQUFJLFdBQVcsQ0FBQyxNQUFNLENBQUM7WUFDbkMsQ0FBQztZQUNELElBQUksV0FBVyxDQUFDLGFBQWEsS0FBSyxhQUFhLEVBQUUsQ0FBQztnQkFDaEQsV0FBVyxJQUFJLFdBQVcsQ0FBQyxNQUFNLENBQUM7WUFDcEMsQ0FBQztRQUNILENBQUM7UUFFRCw4Q0FBOEM7UUFDOUMsUUFBUSxPQUFPLENBQUMsV0FBVyxFQUFFLENBQUM7WUFDNUIsS0FBSyxPQUFPLENBQUM7WUFDYixLQUFLLFNBQVM7Z0JBQ1osT0FBTyxVQUFVLEdBQUcsV0FBVyxDQUFDO1lBQ2xDLEtBQUssV0FBVyxDQUFDO1lBQ2pCLEtBQUssUUFBUSxDQUFDO1lBQ2QsS0FBSyxTQUFTO2dCQUNaLE9BQU8sV0FBVyxHQUFHLFVBQVUsQ0FBQztRQUNwQyxDQUFDO0lBQ0gsQ0FBQztJQUVEOztPQUVHO0lBQ0ksbUJBQW1CLENBQ3hCLFdBQW1CLEVBQ25CLFlBQW9CO1FBRXBCLE9BQU8sSUFBSSxDQUFDLEdBQUcsQ0FBQyxXQUFXLEdBQUcsWUFBWSxDQUFDLEdBQUcsSUFBSSxDQUFDO0lBQ3JELENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQyx5QkFBeUI7UUFDcEMsOEVBQThFO1FBQzlFLDJDQUEyQztRQUMzQyxNQUFNLGVBQWUsR0FBRyxNQUFNLFdBQVcsQ0FBQyxZQUFZLENBQUM7WUFDckQsT0FBTyxFQUFFLElBQUksQ0FBQyxPQUFPO1lBQ3JCLE1BQU0sRUFBRSxRQUFRO1NBQ2pCLENBQUMsQ0FBQztRQUVILHVEQUF1RDtRQUN2RCxNQUFNLFVBQVUsR0FBa0IsRUFBRSxDQUFDO1FBRXJDLG9EQUFvRDtRQUNwRCw4Q0FBOEM7UUFDOUMsd0RBQXdEO1FBRXhELE9BQU8sVUFBVSxDQUFDO0lBQ3BCLENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQyxzQkFBc0I7UUFDakMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsTUFBTSxFQUFFLG9DQUFvQyxDQUFDLENBQUM7UUFFOUQsbUJBQW1CO1FBQ25CLE1BQU0sUUFBUSxHQUFHLE1BQU0sT0FBTyxDQUFDLFlBQVksQ0FBQyxFQUFFLE9BQU8sRUFBRSxJQUFJLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQztRQUV2RSxLQUFLLE1BQU0sT0FBTyxJQUFJLFFBQVEsRUFBRSxDQUFDO1lBQy9CLGlCQUFpQjtZQUNqQixPQUFPLENBQUMsVUFBVSxHQUFHLENBQUMsQ0FBQztZQUN2QixPQUFPLENBQUMsV0FBVyxHQUFHLENBQUMsQ0FBQztZQUN4QixPQUFPLENBQUMsT0FBTyxHQUFHLENBQUMsQ0FBQztZQUVwQix3Q0FBd0M7WUFDeEMsTUFBTSxZQUFZLEdBQUcsTUFBTSxXQUFXLENBQUMsd0JBQXdCLENBQzdELE9BQU8sQ0FBQyxhQUFhLEVBQ3JCLElBQUksQ0FBQyxPQUFPLENBQ2IsQ0FBQztZQUVGLHFCQUFxQjtZQUNyQixLQUFLLE1BQU0sV0FBVyxJQUFJLFlBQVksRUFBRSxDQUFDO2dCQUN2QyxJQUFJLFdBQVcsQ0FBQyxZQUFZLEtBQUssT0FBTyxDQUFDLGFBQWEsRUFBRSxDQUFDO29CQUN2RCxPQUFPLENBQUMsVUFBVSxJQUFJLFdBQVcsQ0FBQyxNQUFNLENBQUM7Z0JBQzNDLENBQUM7Z0JBQ0QsSUFBSSxXQUFXLENBQUMsYUFBYSxLQUFLLE9BQU8sQ0FBQyxhQUFhLEVBQUUsQ0FBQztvQkFDeEQsT0FBTyxDQUFDLFdBQVcsSUFBSSxXQUFXLENBQUMsTUFBTSxDQUFDO2dCQUM1QyxDQUFDO1lBQ0gsQ0FBQztZQUVELDBDQUEwQztZQUMxQyxRQUFRLE9BQU8sQ0FBQyxXQUFXLEVBQUUsQ0FBQztnQkFDNUIsS0FBSyxPQUFPLENBQUM7Z0JBQ2IsS0FBSyxTQUFTO29CQUNaLE9BQU8sQ0FBQyxPQUFPLEdBQUcsT0FBTyxDQUFDLFVBQVUsR0FBRyxPQUFPLENBQUMsV0FBVyxDQUFDO29CQUMzRCxNQUFNO2dCQUNSLEtBQUssV0FBVyxDQUFDO2dCQUNqQixLQUFLLFFBQVEsQ0FBQztnQkFDZCxLQUFLLFNBQVM7b0JBQ1osT0FBTyxDQUFDLE9BQU8sR0FBRyxPQUFPLENBQUMsV0FBVyxHQUFHLE9BQU8sQ0FBQyxVQUFVLENBQUM7b0JBQzNELE1BQU07WUFDVixDQUFDO1lBRUQsT0FBTyxDQUFDLFNBQVMsR0FBRyxJQUFJLElBQUksRUFBRSxDQUFDO1lBQy9CLE1BQU0sT0FBTyxDQUFDLElBQUksRUFBRSxDQUFDO1FBQ3ZCLENBQUM7UUFFRCxJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FDYixNQUFNLEVBQ04sNkJBQTZCLFFBQVEsQ0FBQyxNQUFNLFdBQVcsQ0FDeEQsQ0FBQztJQUNKLENBQUM7Q0FDRiJ9