@rudderstack/integrations-lib
Version:
771 lines • 94.6 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const axios_mock_adapter_1 = __importDefault(require("axios-mock-adapter"));
const axios_1 = __importStar(require("axios"));
const googleAds_1 = __importDefault(require("./googleAds"));
describe('Google ads SDK', () => {
let mock;
beforeEach(() => {
mock = new axios_mock_adapter_1.default(axios_1.default, { onNoMatch: 'throwException' });
});
afterEach(() => {
mock.restore();
});
describe('Test getConversionAction function', () => {
const testCases = [
{
name: 'should return conversion action ID when API returns success response',
authObject: {
accessToken: 'test-access-token',
customerId: '123456789',
developerToken: 'test-developer-token',
},
mockResponse: [
{
results: [
{
conversionAction: {
resourceName: 'customers/123456789/conversionActions/9876543',
id: '9876543',
},
},
],
},
],
conversionName: 'test_conversion',
status: 200,
expectedResult: 'customers/123456789/conversionActions/9876543',
expectedRequest: {
url: '/123456789/googleAds:searchStream',
data: JSON.stringify({
query: "SELECT conversion_action.id FROM conversion_action WHERE conversion_action.name = 'test_conversion'",
}),
},
},
{
name: 'handle failure scenario when accessToken is wrong',
authObject: {
customerId: '123456789',
developerToken: 'test-developer-token',
accessToken: 'wrong-access-token',
},
mockResponse: [
{
error: {
code: 401,
message: 'Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.',
status: 'UNAUTHENTICATED',
},
},
],
mockResponseHeader: new axios_1.AxiosHeaders({
'transfer-encoding': 'chunked',
}),
conversionName: 'test_conversion',
status: 401,
expectedResult: {
headers: new axios_1.AxiosHeaders({
'transfer-encoding': 'chunked',
}),
message: undefined,
responseBody: [
{
error: {
code: 401,
message: 'Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.',
status: 'UNAUTHENTICATED',
},
},
],
statusCode: 401,
type: 'application-error',
},
expectedRequest: {
url: '/123456789/googleAds:searchStream',
data: JSON.stringify({
query: "SELECT conversion_action.id FROM conversion_action WHERE conversion_action.name = 'test_conversion'",
}),
},
},
{
name: 'handle the situation when the conversion is not available',
authObject: {
accessToken: 'test-access-token',
customerId: '123456789',
developerToken: 'test-developer-token',
},
mockResponse: [],
conversionName: 'non-existing-conversion',
status: 200,
expectedResult: null,
expectedRequest: {
url: '/123456789/googleAds:searchStream',
data: JSON.stringify({
query: "SELECT conversion_action.id FROM conversion_action WHERE conversion_action.name = 'non-existing-conversion'",
}),
},
},
];
testCases.forEach(({ name, authObject, mockResponse, conversionName, expectedRequest, expectedResult, status, mockResponseHeader, }) => {
it(name, async () => {
const googleAds = new googleAds_1.default(authObject);
mock
.onPost('https://googleads.googleapis.com/v19/customers/123456789/googleAds:searchStream')
.reply(status, mockResponse, mockResponseHeader);
const result = await googleAds.getConversionActionId(conversionName);
expect(mock.history.post[0]).toMatchObject(expectedRequest);
expect(result).toEqual(expectedResult);
});
});
});
describe('Test getCustomVariable function', () => {
const testCases = [
{
name: 'should return an array of CustomVariableResults when API call succeeds',
authObject: {
accessToken: 'test-access-token',
customerId: '123456789',
developerToken: 'test-developer-token',
},
mockResponse: [
{
results: [
{
conversionCustomVariable: {
resourceName: 'customers/123/conversionCustomVariables/456',
name: 'test_variable',
},
},
],
fieldMask: 'conversion_custom_variable.name',
requestId: '123456',
queryResourceConsumption: '100',
},
],
status: 200,
expectedResult: [
{
conversionCustomVariable: {
resourceName: 'customers/123/conversionCustomVariables/456',
name: 'test_variable',
},
},
],
expectedRequest: {
url: '/123456789/googleAds:searchStream',
data: JSON.stringify({
query: 'SELECT conversion_custom_variable.name FROM conversion_custom_variable',
}),
},
},
{
name: 'should handle the scenario when there is not any customVariables',
authObject: {
accessToken: 'test-access-token',
customerId: '123456789',
developerToken: 'test-developer-token',
},
status: 200,
mockResponse: [
{
results: [],
fieldMask: 'conversion_custom_variable.name',
requestId: '123456',
queryResourceConsumption: '100',
},
],
expectedResult: [],
expectedRequest: {
url: '/123456789/googleAds:searchStream',
data: JSON.stringify({
query: 'SELECT conversion_custom_variable.name FROM conversion_custom_variable',
}),
},
},
{
name: 'should return an array of CustomVariableResults when API call succeeds',
authObject: {
accessToken: 'wrong-access-token',
customerId: '123456789',
developerToken: 'test-developer-token',
},
mockResponse: [
{
error: {
code: 401,
message: 'Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.',
status: 'UNAUTHENTICATED',
},
},
],
status: 401,
expectedResult: {
headers: new axios_1.AxiosHeaders({
'transfer-encoding': 'chunked',
}),
message: undefined,
responseBody: [
{
error: {
code: 401,
message: 'Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.',
status: 'UNAUTHENTICATED',
},
},
],
statusCode: 401,
type: 'application-error',
},
expectedRequest: {
url: '/123456789/googleAds:searchStream',
data: JSON.stringify({
query: 'SELECT conversion_custom_variable.name FROM conversion_custom_variable',
}),
},
mockResponseHeader: new axios_1.AxiosHeaders({
'transfer-encoding': 'chunked',
}),
},
];
testCases.forEach(({ name, authObject, mockResponse, expectedRequest, expectedResult, status, mockResponseHeader, }) => {
it(name, async () => {
const googleAds = new googleAds_1.default(authObject);
mock
.onPost('https://googleads.googleapis.com/v19/customers/123456789/googleAds:searchStream')
.reply(status, mockResponse, mockResponseHeader);
const result = await googleAds.getCustomVariable();
expect(mock.history.post[0]).toMatchObject(expectedRequest);
expect(result).toEqual(expectedResult);
});
});
});
describe('Test addConversionAdjustMent function', () => {
const TestCases = [
{
name: 'should successfully upload conversion adjustments with valid data',
authObject: {
accessToken: 'valid-access-token',
customerId: '123456789',
developerToken: 'test-developer-token',
},
requestData: {
conversionAdjustments: [
{
gclidDateTimePair: {
gclid: 'test-gclid',
conversionDateTime: '2023-01-01T00:00:00Z',
},
adjustmentType: 'RESTATEMENT',
restatementValue: {
adjustedValue: 100,
currencyCode: 'USD',
},
userIdentifiers: [
{
userIdentifierSource: 'FIRST_PARTY',
hashedEmail: '389205904d6c7bb83fc676513911226f2be25bf1465616bb9b29587100ab1414',
},
],
},
],
partialFailure: false,
},
status: 200,
mockResponse: {
conversionAdjustments: [
{
gclidDateTimePair: {
gclid: 'test-gclid',
conversionDateTime: '2023-01-01T00:00:00Z',
},
adjustmentType: 'RESTATEMENT',
restatementValue: {
adjustedValue: 100,
currencyCode: 'USD',
},
userIdentifiers: [],
},
],
partialFailure: false,
},
expectedResult: {
type: 'success',
statusCode: 200,
responseBody: {
conversionAdjustments: [
{
gclidDateTimePair: {
gclid: 'test-gclid',
conversionDateTime: '2023-01-01T00:00:00Z',
},
adjustmentType: 'RESTATEMENT',
restatementValue: { adjustedValue: 100, currencyCode: 'USD' },
userIdentifiers: [],
},
],
partialFailure: false,
},
headers: new axios_1.AxiosHeaders({ 'Content-Type': 'application/json' }),
},
mockResponseHeader: new axios_1.AxiosHeaders({ 'Content-Type': 'application/json' }),
},
{
name: 'should successfully handle failure scenario',
authObject: {
accessToken: 'wrong-access-token',
customerId: '123456789',
developerToken: 'test-developer-token',
},
requestData: {
conversionAdjustments: [
{
gclidDateTimePair: {
gclid: 'test-gclid',
conversionDateTime: '2023-01-01T00:00:00Z',
},
adjustmentType: 'RESTATEMENT',
restatementValue: {
adjustedValue: 100,
currencyCode: 'USD',
},
userIdentifiers: [
{
userIdentifierSource: 'FIRST_PARTY',
hashedEmail: '389205904d6c7bb83fc676513911226f2be25bf1465616bb9b29587100ab1414',
},
],
},
],
partialFailure: false,
},
status: 401,
mockResponse: {
headers: new axios_1.AxiosHeaders({
'transfer-encoding': 'chunked',
}),
message: undefined,
responseBody: [
{
error: {
code: 401,
message: 'Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.',
status: 'UNAUTHENTICATED',
},
},
],
statusCode: 401,
type: 'application-error',
},
expectedResult: {
headers: new axios_1.AxiosHeaders({
'Content-Type': 'application/json',
}),
message: undefined,
responseBody: {
headers: { 'transfer-encoding': 'chunked' },
responseBody: [
{
error: {
code: 401,
message: 'Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.',
status: 'UNAUTHENTICATED',
},
},
],
statusCode: 401,
type: 'application-error',
},
statusCode: 401,
type: 'application-error',
},
mockResponseHeader: new axios_1.AxiosHeaders({ 'Content-Type': 'application/json' }),
},
];
TestCases.forEach(({ name, authObject, mockResponse, requestData, expectedResult, mockResponseHeader, status, }) => {
it(name, async () => {
const googleAds = new googleAds_1.default(authObject);
mock
.onPost('https://googleads.googleapis.com/v19/customers/123456789:uploadConversionAdjustments')
.reply(status, mockResponse, mockResponseHeader);
const result = await googleAds.addConversionAdjustMent(requestData);
expect(result).toEqual(expectedResult);
});
});
});
describe('Test createOfflineUserDataJob function', () => {
const TestCases = [
{
name: 'should return job ID when response type is success for customerMatchUserList job',
authObject: {
accessToken: 'valid-access-token',
customerId: '123456789',
developerToken: 'test-developer-token',
loginCustomerId: '987654321',
},
requestData: {
job: {
type: 'CUSTOMER_MATCH_USER_LIST',
customerMatchUserListMetadata: {
userList: 'customers/123456789/userLists/1234',
},
},
},
status: 200,
mockResponse: {
resourceName: 'customers/123456789/offlineUserDataJobs/5678',
},
expectedResult: {
headers: new axios_1.AxiosHeaders({ 'Content-Type': 'application/json' }),
responseBody: {
resourceName: 'customers/123456789/offlineUserDataJobs/5678',
},
statusCode: 200,
type: 'success',
},
mockResponseHeader: new axios_1.AxiosHeaders({ 'Content-Type': 'application/json' }),
},
{
name: 'should return job ID when response type is success for store sales job',
authObject: {
accessToken: 'valid-access-token',
customerId: '123456789',
developerToken: 'test-developer-token',
},
requestData: {
job: {
storeSalesMetadata: {
custom_key: 'CUSTOM_KEY',
loyaltyFraction: 1,
transaction_upload_fraction: '1',
},
type: 'STORE_SALES_UPLOAD_FIRST_PARTY',
},
},
status: 200,
mockResponse: {
resourceName: 'customers/123456789/offlineUserDataJobs/5678',
},
expectedResult: {
headers: new axios_1.AxiosHeaders({ 'Content-Type': 'application/json' }),
responseBody: {
resourceName: 'customers/123456789/offlineUserDataJobs/5678',
},
statusCode: 200,
type: 'success',
},
mockResponseHeader: new axios_1.AxiosHeaders({ 'Content-Type': 'application/json' }),
},
];
TestCases.forEach(({ name, authObject, mockResponse, status, requestData, expectedResult, mockResponseHeader, }) => {
it(name, async () => {
const googleAds = new googleAds_1.default(authObject);
mock
.onPost('https://googleads.googleapis.com/v19/customers/123456789/offlineUserDataJobs:create')
.reply(status, mockResponse, mockResponseHeader);
const result = await googleAds.createOfflineUserDataJob(requestData);
expect(result).toEqual(expectedResult);
});
});
});
describe('Test addUserToOfflineUserDataJob function', () => {
const TestCases = [
{
name: 'should successfully add user data to an offline user data job',
authObject: {
accessToken: 'valid-access-token',
customerId: '123456789',
developerToken: 'test-developer-token',
loginCustomerId: '987654321',
},
jobId: '1234',
requestData: {
operations: [
{
create: {
userIdentifiers: [{ hashedEmail: 'hashed_email_value' }],
},
},
],
enablePartialFailure: false,
},
status: 200,
mockResponse: {},
expectedResult: {
headers: new axios_1.AxiosHeaders({ 'Content-Type': 'application/json' }),
responseBody: {},
statusCode: 200,
type: 'success',
},
mockResponseHeader: new axios_1.AxiosHeaders({ 'Content-Type': 'application/json' }),
},
];
TestCases.forEach(({ name, authObject, mockResponse, status, requestData, expectedResult, mockResponseHeader, jobId, }) => {
it(name, async () => {
const googleAds = new googleAds_1.default(authObject);
mock
.onPost('https://googleads.googleapis.com/v19/customers/123456789/offlineUserDataJobs/1234:addOperations')
.reply(status, mockResponse, mockResponseHeader);
const result = await googleAds.addUserToOfflineUserDataJob(jobId, requestData);
expect(result).toEqual(expectedResult);
});
});
});
describe('Test addConversionsToOfflineUserDataJob function', () => {
const TestCases = [
{
name: 'should successfully add store sales conversion data to an offline user data job',
authObject: {
accessToken: 'valid-access-token',
customerId: '123456789',
developerToken: 'test-developer-token',
loginCustomerId: '987654321',
},
jobId: '1234',
requestData: {
operations: [
{
create: {
userIdentifiers: [{ hashedEmail: 'hashed-email-value' }],
transactionInfo: {
conversionAction: 'customers/123456789/conversionActions/987',
conversionDateTime: '2023-01-01 12:00:00',
conversionValue: 10.99,
currencyCode: 'USD',
},
},
},
],
enablePartialFailure: true,
},
status: 200,
mockResponse: {},
expectedResult: {
headers: new axios_1.AxiosHeaders({ 'Content-Type': 'application/json' }),
responseBody: {},
statusCode: 200,
type: 'success',
},
mockResponseHeader: new axios_1.AxiosHeaders({ 'Content-Type': 'application/json' }),
},
];
TestCases.forEach(({ name, authObject, mockResponse, status, requestData, expectedResult, mockResponseHeader, jobId, }) => {
it(name, async () => {
const googleAds = new googleAds_1.default(authObject);
mock
.onPost('https://googleads.googleapis.com/v19/customers/123456789/offlineUserDataJobs/1234:addOperations')
.reply(status, mockResponse, mockResponseHeader);
const result = await googleAds.addConversionsToOfflineUserDataJob(jobId, requestData);
expect(result).toEqual(expectedResult);
});
});
});
describe('Test runOfflineUserDataJob function', () => {
const TestCases = [
{
name: 'should successfully run an offline user data job with valid jobId',
authObject: {
accessToken: 'valid-access-token',
customerId: '123456789',
developerToken: 'test-developer-token',
loginCustomerId: '987654321',
},
jobId: '1234',
requestData: {},
status: 200,
mockResponse: {
success: true,
data: { name: 'test-job', done: true },
},
expectedResult: {
headers: new axios_1.AxiosHeaders({ 'Content-Type': 'application/json' }),
responseBody: {
success: true,
data: { name: 'test-job', done: true },
},
statusCode: 200,
type: 'success',
},
mockResponseHeader: new axios_1.AxiosHeaders({ 'Content-Type': 'application/json' }),
},
];
TestCases.forEach(({ name, authObject, mockResponse, status, expectedResult, mockResponseHeader, jobId }) => {
it(name, async () => {
const googleAds = new googleAds_1.default(authObject);
mock
.onPost('https://googleads.googleapis.com/v19/customers/123456789/offlineUserDataJobs/1234:run')
.reply(status, mockResponse, mockResponseHeader);
const result = await googleAds.runOfflineUserDataJob(jobId);
expect(result).toEqual(expectedResult);
});
});
});
describe('Test uploadClickConversion function', () => {
const TestCases = [
{
name: 'should successfully add user data to an offline user data job',
authObject: {
accessToken: 'valid-access-token',
customerId: '123456789',
developerToken: 'test-developer-token',
loginCustomerId: '987654321',
},
requestData: {
conversions: [
{
gclid: 'test-gclid',
conversionAction: 'test-action',
conversionDateTime: '2023-01-01T00:00:00Z',
conversionValue: 100,
currencyCode: 'USD',
},
],
},
status: 200,
mockResponse: {
success: true,
data: {
results: [
{
gclid: 'test-gclid',
conversionAction: 'test-action',
conversionDateTime: '2023-01-01T00:00:00Z',
},
],
},
},
expectedResult: {
headers: new axios_1.AxiosHeaders({ 'Content-Type': 'application/json' }),
responseBody: {
data: {
results: [
{
conversionAction: 'test-action',
conversionDateTime: '2023-01-01T00:00:00Z',
gclid: 'test-gclid',
},
],
},
success: true,
},
statusCode: 200,
type: 'success',
},
mockResponseHeader: new axios_1.AxiosHeaders({ 'Content-Type': 'application/json' }),
},
];
TestCases.forEach(({ name, authObject, mockResponse, status, expectedResult, mockResponseHeader, requestData, }) => {
it(name, async () => {
const googleAds = new googleAds_1.default(authObject);
mock
.onPost('https://googleads.googleapis.com/v19/customers/123456789:uploadClickConversions')
.reply(status, mockResponse, mockResponseHeader);
const result = await googleAds.uploadClickConversion(requestData);
expect(result).toEqual(expectedResult);
});
});
});
describe('Test uploadCallConversion function', () => {
const TestCases = [
{
name: 'should successfully upload call conversion data and return a successful response',
authObject: {
accessToken: 'valid-access-token',
customerId: '123456789',
developerToken: 'test-developer-token',
loginCustomerId: '987654321',
},
requestData: {
conversions: [
{
callerId: '1234567890',
callStartDateTime: '2023-01-01T12:00:00Z',
conversionAction: 'action1',
conversionDateTime: '2023-01-01T12:05:00Z',
},
],
partialFailure: false,
},
status: 200,
mockResponse: {
success: true,
data: {
results: [
{
callerId: '1234567890',
callStartDateTime: '2023-01-01T12:00:00Z',
conversionAction: 'action1',
conversionDateTime: '2023-01-01T12:05:00Z',
},
],
},
},
expectedResult: {
headers: new axios_1.AxiosHeaders({ 'Content-Type': 'application/json' }),
responseBody: {
data: {
results: [
{
callStartDateTime: '2023-01-01T12:00:00Z',
callerId: '1234567890',
conversionAction: 'action1',
conversionDateTime: '2023-01-01T12:05:00Z',
},
],
},
success: true,
},
statusCode: 200,
type: 'success',
},
mockResponseHeader: new axios_1.AxiosHeaders({ 'Content-Type': 'application/json' }),
},
];
TestCases.forEach(({ name, authObject, mockResponse, status, expectedResult, mockResponseHeader, requestData, }) => {
it(name, async () => {
const googleAds = new googleAds_1.default(authObject);
mock
.onPost('https://googleads.googleapis.com/v19/customers/123456789:uploadClickConversions')
.reply(status, mockResponse, mockResponseHeader);
const result = await googleAds.uploadClickConversion(requestData);
expect(result).toEqual(expectedResult);
});
});
});
});
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ29vZ2xlQWRzLnRlc3QuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvc2Rrcy9nb29nbGVBZHNSZXN0QVBJL2dvb2dsZUFkcy50ZXN0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQUEsNEVBQWtEO0FBQ2xELCtDQUE0QztBQUM1Qyw0REFBb0M7QUFFcEMsUUFBUSxDQUFDLGdCQUFnQixFQUFFLEdBQUcsRUFBRTtJQUM5QixJQUFJLElBQXNCLENBQUM7SUFFM0IsVUFBVSxDQUFDLEdBQUcsRUFBRTtRQUNkLElBQUksR0FBRyxJQUFJLDRCQUFnQixDQUFDLGVBQUssRUFBRSxFQUFFLFNBQVMsRUFBRSxnQkFBZ0IsRUFBRSxDQUFDLENBQUM7SUFDdEUsQ0FBQyxDQUFDLENBQUM7SUFFSCxTQUFTLENBQUMsR0FBRyxFQUFFO1FBQ2IsSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDO0lBQ2pCLENBQUMsQ0FBQyxDQUFDO0lBQ0gsUUFBUSxDQUFDLG1DQUFtQyxFQUFFLEdBQUcsRUFBRTtRQUNqRCxNQUFNLFNBQVMsR0FBRztZQUNoQjtnQkFDRSxJQUFJLEVBQUUsc0VBQXNFO2dCQUM1RSxVQUFVLEVBQUU7b0JBQ1YsV0FBVyxFQUFFLG1CQUFtQjtvQkFDaEMsVUFBVSxFQUFFLFdBQVc7b0JBQ3ZCLGNBQWMsRUFBRSxzQkFBc0I7aUJBQ3ZDO2dCQUNELFlBQVksRUFBRTtvQkFDWjt3QkFDRSxPQUFPLEVBQUU7NEJBQ1A7Z0NBQ0UsZ0JBQWdCLEVBQUU7b0NBQ2hCLFlBQVksRUFBRSwrQ0FBK0M7b0NBQzdELEVBQUUsRUFBRSxTQUFTO2lDQUNkOzZCQUNGO3lCQUNGO3FCQUNGO2lCQUNGO2dCQUNELGNBQWMsRUFBRSxpQkFBaUI7Z0JBQ2pDLE1BQU0sRUFBRSxHQUFHO2dCQUNYLGNBQWMsRUFBRSwrQ0FBK0M7Z0JBQy9ELGVBQWUsRUFBRTtvQkFDZixHQUFHLEVBQUUsbUNBQW1DO29CQUN4QyxJQUFJLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQzt3QkFDbkIsS0FBSyxFQUNILHFHQUFxRztxQkFDeEcsQ0FBQztpQkFDSDthQUNGO1lBQ0Q7Z0JBQ0UsSUFBSSxFQUFFLG1EQUFtRDtnQkFDekQsVUFBVSxFQUFFO29CQUNWLFVBQVUsRUFBRSxXQUFXO29CQUN2QixjQUFjLEVBQUUsc0JBQXNCO29CQUN0QyxXQUFXLEVBQUUsb0JBQW9CO2lCQUNsQztnQkFDRCxZQUFZLEVBQUU7b0JBQ1o7d0JBQ0UsS0FBSyxFQUFFOzRCQUNMLElBQUksRUFBRSxHQUFHOzRCQUNULE9BQU8sRUFDTCxrTkFBa047NEJBQ3BOLE1BQU0sRUFBRSxpQkFBaUI7eUJBQzFCO3FCQUNGO2lCQUNGO2dCQUNELGtCQUFrQixFQUFFLElBQUksb0JBQVksQ0FBQztvQkFDbkMsbUJBQW1CLEVBQUUsU0FBUztpQkFDL0IsQ0FBQztnQkFDRixjQUFjLEVBQUUsaUJBQWlCO2dCQUNqQyxNQUFNLEVBQUUsR0FBRztnQkFDWCxjQUFjLEVBQUU7b0JBQ2QsT0FBTyxFQUFFLElBQUksb0JBQVksQ0FBQzt3QkFDeEIsbUJBQW1CLEVBQUUsU0FBUztxQkFDL0IsQ0FBQztvQkFDRixPQUFPLEVBQUUsU0FBUztvQkFDbEIsWUFBWSxFQUFFO3dCQUNaOzRCQUNFLEtBQUssRUFBRTtnQ0FDTCxJQUFJLEVBQUUsR0FBRztnQ0FDVCxPQUFPLEVBQ0wsa05BQWtOO2dDQUNwTixNQUFNLEVBQUUsaUJBQWlCOzZCQUMxQjt5QkFDRjtxQkFDRjtvQkFDRCxVQUFVLEVBQUUsR0FBRztvQkFDZixJQUFJLEVBQUUsbUJBQW1CO2lCQUMxQjtnQkFDRCxlQUFlLEVBQUU7b0JBQ2YsR0FBRyxFQUFFLG1DQUFtQztvQkFDeEMsSUFBSSxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUM7d0JBQ25CLEtBQUssRUFDSCxxR0FBcUc7cUJBQ3hHLENBQUM7aUJBQ0g7YUFDRjtZQUNEO2dCQUNFLElBQUksRUFBRSwyREFBMkQ7Z0JBQ2pFLFVBQVUsRUFBRTtvQkFDVixXQUFXLEVBQUUsbUJBQW1CO29CQUNoQyxVQUFVLEVBQUUsV0FBVztvQkFDdkIsY0FBYyxFQUFFLHNCQUFzQjtpQkFDdkM7Z0JBQ0QsWUFBWSxFQUFFLEVBQUU7Z0JBQ2hCLGNBQWMsRUFBRSx5QkFBeUI7Z0JBQ3pDLE1BQU0sRUFBRSxHQUFHO2dCQUNYLGNBQWMsRUFBRSxJQUFJO2dCQUNwQixlQUFlLEVBQUU7b0JBQ2YsR0FBRyxFQUFFLG1DQUFtQztvQkFDeEMsSUFBSSxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUM7d0JBQ25CLEtBQUssRUFDSCw2R0FBNkc7cUJBQ2hILENBQUM7aUJBQ0g7YUFDRjtTQUNGLENBQUM7UUFDRixTQUFTLENBQUMsT0FBTyxDQUNmLENBQUMsRUFDQyxJQUFJLEVBQ0osVUFBVSxFQUNWLFlBQVksRUFDWixjQUFjLEVBQ2QsZUFBZSxFQUNmLGNBQWMsRUFDZCxNQUFNLEVBQ04sa0JBQWtCLEdBQ25CLEVBQUUsRUFBRTtZQUNILEVBQUUsQ0FBQyxJQUFJLEVBQUUsS0FBSyxJQUFJLEVBQUU7Z0JBQ2xCLE1BQU0sU0FBUyxHQUFHLElBQUksbUJBQVMsQ0FBQyxVQUFVLENBQUMsQ0FBQztnQkFDNUMsSUFBSTtxQkFDRCxNQUFNLENBQ0wsaUZBQWlGLENBQ2xGO3FCQUNBLEtBQUssQ0FBQyxNQUFNLEVBQUUsWUFBWSxFQUFFLGtCQUFrQixDQUFDLENBQUM7Z0JBQ25ELE1BQU0sTUFBTSxHQUFHLE1BQU0sU0FBUyxDQUFDLHFCQUFxQixDQUFDLGNBQWMsQ0FBQyxDQUFDO2dCQUNyRSxNQUFNLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxhQUFhLENBQUMsZUFBZSxDQUFDLENBQUM7Z0JBQzVELE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQyxPQUFPLENBQUMsY0FBYyxDQUFDLENBQUM7WUFDekMsQ0FBQyxDQUFDLENBQUM7UUFDTCxDQUFDLENBQ0YsQ0FBQztJQUNKLENBQUMsQ0FBQyxDQUFDO0lBRUgsUUFBUSxDQUFDLGlDQUFpQyxFQUFFLEdBQUcsRUFBRTtRQUMvQyxNQUFNLFNBQVMsR0FBRztZQUNoQjtnQkFDRSxJQUFJLEVBQUUsd0VBQXdFO2dCQUM5RSxVQUFVLEVBQUU7b0JBQ1YsV0FBVyxFQUFFLG1CQUFtQjtvQkFDaEMsVUFBVSxFQUFFLFdBQVc7b0JBQ3ZCLGNBQWMsRUFBRSxzQkFBc0I7aUJBQ3ZDO2dCQUNELFlBQVksRUFBRTtvQkFDWjt3QkFDRSxPQUFPLEVBQUU7NEJBQ1A7Z0NBQ0Usd0JBQXdCLEVBQUU7b0NBQ3hCLFlBQVksRUFBRSw2Q0FBNkM7b0NBQzNELElBQUksRUFBRSxlQUFlO2lDQUN0Qjs2QkFDRjt5QkFDRjt3QkFDRCxTQUFTLEVBQUUsaUNBQWlDO3dCQUM1QyxTQUFTLEVBQUUsUUFBUTt3QkFDbkIsd0JBQXdCLEVBQUUsS0FBSztxQkFDaEM7aUJBQ0Y7Z0JBQ0QsTUFBTSxFQUFFLEdBQUc7Z0JBQ1gsY0FBYyxFQUFFO29CQUNkO3dCQUNFLHdCQUF3QixFQUFFOzRCQUN4QixZQUFZLEVBQUUsNkNBQTZDOzRCQUMzRCxJQUFJLEVBQUUsZUFBZTt5QkFDdEI7cUJBQ0Y7aUJBQ0Y7Z0JBQ0QsZUFBZSxFQUFFO29CQUNmLEdBQUcsRUFBRSxtQ0FBbUM7b0JBQ3hDLElBQUksRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDO3dCQUNuQixLQUFLLEVBQUUsd0VBQXdFO3FCQUNoRixDQUFDO2lCQUNIO2FBQ0Y7WUFDRDtnQkFDRSxJQUFJLEVBQUUsa0VBQWtFO2dCQUN4RSxVQUFVLEVBQUU7b0JBQ1YsV0FBVyxFQUFFLG1CQUFtQjtvQkFDaEMsVUFBVSxFQUFFLFdBQVc7b0JBQ3ZCLGNBQWMsRUFBRSxzQkFBc0I7aUJBQ3ZDO2dCQUNELE1BQU0sRUFBRSxHQUFHO2dCQUNYLFlBQVksRUFBRTtvQkFDWjt3QkFDRSxPQUFPLEVBQUUsRUFBRTt3QkFDWCxTQUFTLEVBQUUsaUNBQWlDO3dCQUM1QyxTQUFTLEVBQUUsUUFBUTt3QkFDbkIsd0JBQXdCLEVBQUUsS0FBSztxQkFDaEM7aUJBQ0Y7Z0JBQ0QsY0FBYyxFQUFFLEVBQUU7Z0JBQ2xCLGVBQWUsRUFBRTtvQkFDZixHQUFHLEVBQUUsbUNBQW1DO29CQUN4QyxJQUFJLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQzt3QkFDbkIsS0FBSyxFQUFFLHdFQUF3RTtxQkFDaEYsQ0FBQztpQkFDSDthQUNGO1lBQ0Q7Z0JBQ0UsSUFBSSxFQUFFLHdFQUF3RTtnQkFDOUUsVUFBVSxFQUFFO29CQUNWLFdBQVcsRUFBRSxvQkFBb0I7b0JBQ2pDLFVBQVUsRUFBRSxXQUFXO29CQUN2QixjQUFjLEVBQUUsc0JBQXNCO2lCQUN2QztnQkFDRCxZQUFZLEVBQUU7b0JBQ1o7d0JBQ0UsS0FBSyxFQUFFOzRCQUNMLElBQUksRUFBRSxHQUFHOzRCQUNULE9BQU8sRUFDTCxrTkFBa047NEJBQ3BOLE1BQU0sRUFBRSxpQkFBaUI7eUJBQzFCO3FCQUNGO2lCQUNGO2dCQUNELE1BQU0sRUFBRSxHQUFHO2dCQUNYLGNBQWMsRUFBRTtvQkFDZCxPQUFPLEVBQUUsSUFBSSxvQkFBWSxDQUFDO3dCQUN4QixtQkFBbUIsRUFBRSxTQUFTO3FCQUMvQixDQUFDO29CQUNGLE9BQU8sRUFBRSxTQUFTO29CQUNsQixZQUFZLEVBQUU7d0JBQ1o7NEJBQ0UsS0FBSyxFQUFFO2dDQUNMLElBQUksRUFBRSxHQUFHO2dDQUNULE9BQU8sRUFDTCxrTkFBa047Z0NBQ3BOLE1BQU0sRUFBRSxpQkFBaUI7NkJBQzFCO3lCQUNGO3FCQUNGO29CQUNELFVBQVUsRUFBRSxHQUFHO29CQUNmLElBQUksRUFBRSxtQkFBbUI7aUJBQzFCO2dCQUNELGVBQWUsRUFBRTtvQkFDZixHQUFHLEVBQUUsbUNBQW1DO29CQUN4QyxJQUFJLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQzt3QkFDbkIsS0FBSyxFQUFFLHdFQUF3RTtxQkFDaEYsQ0FBQztpQkFDSDtnQkFDRCxrQkFBa0IsRUFBRSxJQUFJLG9CQUFZLENBQUM7b0JBQ25DLG1CQUFtQixFQUFFLFNBQVM7aUJBQy9CLENBQUM7YUFDSDtTQUNGLENBQUM7UUFFRixTQUFTLENBQUMsT0FBTyxDQUNmLENBQUMsRUFDQyxJQUFJLEVBQ0osVUFBVSxFQUNWLFlBQVksRUFDWixlQUFlLEVBQ2YsY0FBYyxFQUNkLE1BQU0sRUFDTixrQkFBa0IsR0FDbkIsRUFBRSxFQUFFO1lBQ0gsRUFBRSxDQUFDLElBQUksRUFBRSxLQUFLLElBQUksRUFBRTtnQkFDbEIsTUFBTSxTQUFTLEdBQUcsSUFBSSxtQkFBUyxDQUFDLFVBQVUsQ0FBQyxDQUFDO2dCQUM1QyxJQUFJO3FCQUNELE1BQU0sQ0FDTCxpRkFBaUYsQ0FDbEY7cUJBQ0EsS0FBSyxDQUFDLE1BQU0sRUFBRSxZQUFZLEVBQUUsa0JBQWtCLENBQUMsQ0FBQztnQkFDbkQsTUFBTSxNQUFNLEdBQUcsTUFBTSxTQUFTLENBQUMsaUJBQWlCLEVBQUUsQ0FBQztnQkFDbkQsTUFBTSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsYUFBYSxDQUFDLGVBQWUsQ0FBQyxDQUFDO2dCQUM1RCxNQUFNLENBQUMsTUFBTSxDQUFDLENBQUMsT0FBTyxDQUFDLGNBQWMsQ0FBQyxDQUFDO1lBQ3pDLENBQUMsQ0FBQyxDQUFDO1FBQ0wsQ0FBQyxDQUNGLENBQUM7SUFDSixDQUFDLENBQUMsQ0FBQztJQUVILFFBQVEsQ0FBQyx1Q0FBdUMsRUFBRSxHQUFHLEVBQUU7UUFDckQsTUFBTSxTQUFTLEdBQUc7WUFDaEI7Z0JBQ0UsSUFBSSxFQUFFLG1FQUFtRTtnQkFDekUsVUFBVSxFQUFFO29CQUNWLFdBQVcsRUFBRSxvQkFBb0I7b0JBQ2pDLFVBQVUsRUFBRSxXQUFXO29CQUN2QixjQUFjLEVBQUUsc0JBQXNCO2lCQUN2QztnQkFDRCxXQUFXLEVBQUU7b0JBQ1gscUJBQXFCLEVBQUU7d0JBQ3JCOzRCQUNFLGlCQUFpQixFQUFFO2dDQUNqQixLQUFLLEVBQUUsWUFBWTtnQ0FDbkIsa0JBQWtCLEVBQUUsc0JBQXNCOzZCQUMzQzs0QkFDRCxjQUFjLEVBQUUsYUFBc0I7NEJBQ3RDLGdCQUFnQixFQUFFO2dDQUNoQixhQUFhLEVBQUUsR0FBRztnQ0FDbEIsWUFBWSxFQUFFLEtBQUs7NkJBQ3BCOzRCQUNELGVBQWUsRUFBRTtnQ0FDZjtvQ0FDRSxvQkFBb0IsRUFBRSxhQUFzQjtvQ0FDNUMsV0FBVyxFQUFFLGtFQUFrRTtpQ0FDaEY7NkJBQ0Y7eUJBQ0Y7cUJBQ0Y7b0JBQ0QsY0FBYyxFQUFFLEtBQUs7aUJBQ3RCO2dCQUNELE1BQU0sRUFBRSxHQUFHO2dCQUNYLFlBQVksRUFBRTtvQkFDWixxQkFBcUIsRUFBRTt3QkFDckI7NEJBQ0UsaUJBQWlCLEVBQUU7Z0NBQ2pCLEtBQUssRUFBRSxZQUFZO2dDQUNuQixrQkFBa0IsRUFBRSxzQkFBc0I7NkJBQzNDOzRCQUNELGNBQWMsRUFBRSxhQUFhOzRCQUM3QixnQkFBZ0IsRUFBRTtnQ0FDaEIsYUFBYSxFQUFFLEdBQUc7Z0NBQ2xCLFlBQVksRUFBRSxLQUFLOzZCQUNwQjs0QkFDRCxlQUFlLEVBQUUsRUFBRTt5QkFDcEI7cUJBQ0Y7b0JBQ0QsY0FBYyxFQUFFLEtBQUs7aUJBQ3RCO2dCQUNELGNBQWMsRUFBRTtvQkFDZCxJQUFJLEVBQUUsU0FBUztvQkFDZixVQUFVLEVBQUUsR0FBRztvQkFDZixZQUFZLEVBQUU7d0JBQ1oscUJBQXFCLEVBQUU7NEJBQ3JCO2dDQUNFLGlCQUFpQixFQUFFO29DQUNqQixLQUFLLEVBQUUsWUFBWTtvQ0FDbkIsa0JBQWtCLEVBQUUsc0JBQXNCO2lDQUMzQztnQ0FDRCxjQUFjLEVBQUUsYUFBYTtnQ0FDN0IsZ0JBQWdCLEVBQUUsRUFBRSxhQUFhLEVBQUUsR0FBRyxFQUFFLFlBQVksRUFBRSxLQUFLLEVBQUU7Z0NBQzdELGVBQWUsRUFBRSxFQUFFOzZCQUNwQjt5QkFDRjt3QkFDRCxjQUFjLEVBQUUsS0FBSztxQkFDdEI7b0JBQ0QsT0FBTyxFQUFFLElBQUksb0JBQVksQ0FBQyxFQUFFLGNBQWMsRUFBRSxrQkFBa0IsRUFBRSxDQUFDO2lCQUNsRTtnQkFDRCxrQkFBa0IsRUFBRSxJQUFJLG9CQUFZLENBQUMsRUFBRSxjQUFjLEVBQUUsa0JBQWtCLEVBQUUsQ0FBQzthQUM3RTtZQUNEO2dCQUNFLElBQUksRUFBRSw2Q0FBNkM7Z0JBQ25ELFVBQVUsRUFBRTtvQkFDVixXQUFXLEVBQUUsb0JBQW9CO29CQUNqQyxVQUFVLEVBQUUsV0FBVztvQkFDdkIsY0FBYyxFQUFFLHNCQUFzQjtpQkFDdkM7Z0JBQ0QsV0FBVyxFQUFFO29CQUNYLHFCQUFxQixFQUFFO3dCQUNyQjs0QkFDRSxpQkFBaUIsRUFBRTtnQ0FDakIsS0FBSyxFQUFFLFlBQVk7Z0NBQ25CLGtCQUFrQixFQUFFLHNCQUFzQjs2QkFDM0M7NEJBQ0QsY0FBYyxFQUFFLGFBQXNCOzRCQUN0QyxnQkFBZ0IsRUFBRTtnQ0FDaEIsYUFBYSxFQUFFLEdBQUc7Z0NBQ2xCLFlBQVksRUFBRSxLQUFLOzZCQUNwQjs0QkFDRCxlQUFlLEVBQUU7Z0NBQ2Y7b0NBQ0Usb0JBQW9CLEVBQUUsYUFBc0I7b0NBQzVDLFdBQVcsRUFBRSxrRUFBa0U7aUNBQ2hGOzZCQUNGO3lCQUNGO3FCQUNGO29CQUNELGNBQWMsRUFBRSxLQUFLO2lCQUN0QjtnQkFDRCxNQUFNLEVBQUUsR0FBRztnQkFDWCxZQUFZLEVBQUU7b0JBQ1osT0FBTyxFQUFFLElBQUksb0JBQVksQ0FBQzt3QkFDeEIsbUJBQW1CLEVBQUUsU0FBUztxQkFDL0IsQ0FBQztvQkFDRixPQUFPLEVBQUUsU0FBUztvQkFDbEIsWUFBWSxFQUFFO3dCQUNaOzRCQUNFLEtBQUssRUFBRTtnQ0FDTCxJQUFJLEVBQUUsR0FBRztnQ0FDVCxPQUFPLEVBQ0wsa05BQWtOO2dDQUNwTixNQUFNLEVBQUUsaUJBQWlCOzZCQUMxQjt5QkFDRjtxQkFDRjtvQkFDRCxVQUFVLEVBQUUsR0FBRztvQkFDZixJQUFJLEVBQUUsbUJBQW1CO2lCQUMxQjtnQkFDRCxjQUFjLEVBQUU7b0JBQ2QsT0FBTyxFQUFFLElBQUksb0JBQVksQ0FBQzt3QkFDeEIsY0FBYyxFQUFFLGtCQUFrQjtxQkFDbkMsQ0FBQztvQkFDRixPQUFPLEVBQUUsU0FBUztvQkFDbEIsWUFBWSxFQUFFO3dCQUNaLE9BQU8sRUFBRSxFQUFFLG1CQUFtQixFQUFFLFNBQVMsRUFBRTt3QkFDM0MsWUFBWSxFQUFFOzRCQUNaO2dDQUNFLEtBQUssRUFBRTtvQ0FDTCxJQUFJLEVBQUUsR0FBRztvQ0FDVCxPQUFPLEVBQ0wsa05BQWtOO29DQUNwTixNQUFNLEVBQUUsaUJBQWlCO2lDQUMxQjs2QkFDRjt5QkFDRjt3QkFDRCxVQUFVLEVBQUUsR0FBRzt3QkFDZixJQUFJLEVBQUUsbUJBQW1CO3FCQUMxQjtvQkFDRCxVQUFVLEVBQUUsR0FBRztvQkFDZixJQUFJLEVBQUUsbUJBQW1CO2lCQUMxQjtnQkFDRCxrQkFBa0IsRUFBRSxJQUFJLG9CQUFZLENBQUMsRUFBRSxjQUFjLEVBQUUsa0JBQWtCLEVBQUUsQ0FBQzthQUM3RTtTQUNGLENBQUM7UUFDRixTQUFTLENBQUMsT0FBTyxDQUNmLENBQUMsRUFDQyxJQUFJLEVBQ0osVUFBVSxFQUNWLFlBQVksRUFDWixXQUFXLEVBQ1gsY0FBYyxFQUNkLGtCQUFrQixFQUNsQixNQUFNLEdBQ1AsRUFBRSxFQUFFO1lBQ0gsRUFBRSxDQUFDLElBQUksRUFBRSxLQUFLLElBQUksRUFBRTtnQkFDbEIsTUFBTSxTQUFTLEdBQUcsSUFBSSxtQkFBUyxDQUFDLFVBQVUsQ0FBQyxDQUFDO2dCQUM1QyxJQUFJO3FCQUNELE1BQU0sQ0FDTCxzRkFBc0YsQ0FDdkY7cUJBQ0EsS0FBSyxDQUFDLE1BQU0sRUFBRSxZQUFZLEVBQUUsa0JBQWtCLENBQUMsQ0FBQztnQkFDbkQsTUFBTSxNQUFNLEdBQUcsTUFBTSxTQUFTLENBQUMsdUJBQXVCLENBQUMsV0FBVyxDQUFDLENBQUM7Z0JBQ3BFLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQyxPQUFPLENBQUMsY0FBYyxDQUFDLENBQUM7WUFDekMsQ0FBQyxDQUFDLENBQUM7UUFDTCxDQUFDLENBQ0YsQ0FBQztJQUNKLENBQUMsQ0FBQyxDQUFDO0lBRUgsUUFBUSxDQUFDLHdDQUF3QyxFQUFFLEdBQUcsRUFBRTtRQUN0RCxNQUFNLFNBQVMsR0FBRztZQUNoQjtnQkFDRSxJQUFJLEVBQUUsa0ZBQWtGO2dCQUN4RixVQUFVLEVBQUU7b0JBQ1YsV0FBVyxFQUFFLG9CQUFvQjtvQkFDakMsVUFBVSxFQUFFLFdBQVc7b0JBQ3ZCLGNBQWMsRUFBRSxzQkFBc0I7b0JBQ3RDLGVBQWUsRUFBRSxXQUFXO2lCQUM3QjtnQkFDRCxXQUFXLEVBQUU7b0JBQ1gsR0FBRyxFQUFFO3dCQUNILElBQUksRUFBRSwwQkFBbUM7d0JBQ3pDLDZCQUE2QixFQUFFOzRCQUM3QixRQUFRLEVBQUUsb0NBQW9DO3lCQUMvQztxQkFDRjtpQkFDRjtnQkFDRCxNQUFNLEVBQUUsR0FBRztnQkFDWCxZQUFZLEVBQUU7b0JBQ1osWUFBWSxFQUFFLDhDQUE4QztpQkFDN0Q7Z0JBQ0QsY0FBYyxFQUFFO29CQUNkLE9BQU8sRUFBRSxJQUFJLG9CQUFZLENBQUMsRUFBRSxjQUFjLEVBQUUsa0JBQWtCLEVBQUUsQ0FBQztvQkFDakUsWUFBWSxFQUFFO3dCQUNaLFlBQVksRUFBRSw4Q0FBOEM7cUJBQzdEO29CQUNELFVBQVUsRUFBRSxHQUFHO29CQUNmLElBQUksRUFBRSxTQUFTO2lCQUNoQjtnQkFDRCxrQkFBa0IsRUFBRSxJQUFJLG9CQUFZLENBQUMsRUFBRSxjQUFjLEVBQUUsa0JBQWtCLEVBQUUsQ0FBQzthQUM3RTtZQUNEO2dCQUNFLElBQUksRUFBRSx3RUFBd0U7Z0JBQzlFLFVBQVUsRUFBRTtvQkFDVixXQUFXLEVBQUUsb0JBQW9CO29CQUNqQyxVQUFVLEVBQUUsV0FBVztvQkFDdkIsY0FBYyxFQUFFLHNCQUFzQjtpQkFDdkM7Z0JBQ0QsV0FBVyxFQUFFO29CQUNYLEdBQUcsRUFBRTt3QkFDSCxrQkFBa0IsRUFBRTs0QkFDbEIsVUFBVSxFQUFFLFlBQVk7NEJBQ3hCLGVBQWUsRUFBRSxDQUFDOzRCQUNsQiwyQkFBMkIsRUFBRSxHQUFHO3lCQUNqQzt3QkFDRCxJQUFJLEVBQUUsZ0NBQXlDO3FCQUNoRDtpQkFDRjtnQkFDRCxNQUFNLEVBQUUsR0FBRztnQkFDWCxZQUFZLEVBQUU7b0JBQ1osWUFBWSxFQUFFLDhDQUE4QztpQkFDN0Q7Z0JBQ0QsY0FBYyxFQUFFO29CQUNkLE9BQU8sRUFBRSxJQUFJLG9CQUFZLENBQUMsRUFBRSxjQUFjLEVBQUUsa0JBQWtCLEVBQUUsQ0FBQztvQkFDakUsWUFBWSxFQUFFO3dCQUNaLFlBQVksRUFBRSw4Q0FBOEM7cUJBQzdEO29CQUNELFVBQVUsRUFBRSxHQUFHO29CQUNmLElBQUksRUFBRSxTQUFTO2lCQUNoQjtnQkFDRCxrQkFBa0IsRUFBRSxJQUFJLG9CQUFZLENBQUMsRUFBRSxjQUFjLEVBQUUsa0JBQWtCLEVBQUUsQ0FBQzthQUM3RTtTQUNGLENBQUM7UUFDRixTQUFTLENBQUMsT0FBTyxDQUNmLENBQUMsRUFDQyxJQUFJLEVBQ0osVUFBVSxFQUNWLFlBQVksRUFDWixNQUFNLEVBQ04sV0FBVyxFQUNYLGNBQWMsRUFDZCxrQkFBa0IsR0FDbkIsRUFBRSxFQUFFO1lBQ0gsRUFBRSxDQUFDLElBQUksRUFBRSxLQUFLLElBQUksRUFBRTtnQkFDbEIsTUFBTSxTQUFTLEdBQUcsSUFBSSxtQkFBUyxDQUFDLFVBQVUsQ0FBQyxDQUFDO2dCQUM1QyxJQUFJO3FCQUNELE1BQU0sQ0FDTCxxRkFBcUYsQ0FDdEY7cUJBQ0EsS0FBSyxDQUFDLE1BQU0sRUFBRSxZQUFZLEVBQUUsa0JBQWtCLENBQUMsQ0FBQztnQkFDbkQsTUFBTSxNQUFNLEdBQUcsTUFBTSxTQUFTLENBQUMsd0JBQXdCLENBQUMsV0FBVyxDQUFDLENBQUM7Z0JBQ3JFLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQyxPQUFPLENBQUMsY0FBYyxDQUFDLENBQUM7WUFDekMsQ0FBQyxDQUFDLENBQUM7UUFDTCxDQUFDLENBQ0YsQ0FBQztJQUNKLENBQUMsQ0FBQyxDQUFDO0lBRUgsUUFBUSxDQUFDLDJDQUEyQyxFQUFFLEdBQUcsRUFBRTtRQUN6RCxNQUFNLFNBQVMsR0FBRztZQUNoQjtnQkFDRSxJQUFJLEVBQUUsK0RBQStEO2dCQUNyRSxVQUFVLEVBQUU7b0JBQ1YsV0FBVyxFQUFFLG9CQUFvQjtvQkFDakMsVUFBVSxFQUFFLFdBQVc7b0JBQ3ZCLGNBQWMsRUFBRSxzQkFBc0I7b0JBQ3RDLGVBQWUsRUFBRSxXQUFXO2lCQUM3QjtnQkFDRCxLQUFLLEVBQUUsTUFBTTtnQkFDYixXQUFXLEVBQUU7b0JBQ1gsVUFBVSxFQUFFO3dCQUNWOzRCQUNFLE1BQU0sRUFBRTtnQ0FDTixlQUFlLEVBQUUsQ0FBQyxFQUFFLFdBQVcsRUFBRSxvQkFBb0IsRUFBRSxDQUFDOzZCQUN6RDt5QkFDRjtxQkFDRjtvQkFDRCxvQkFBb0IsRUFBRSxLQUFLO2lCQUM1QjtnQkFDRCxNQUFNLEVBQUUsR0FBRztnQkFDWCxZQUFZLEVBQUUsRUFBRTtnQkFDaEIsY0FBYyxFQUFFO29CQUNkLE9BQU8sRUFBRSxJQUFJLG9CQUFZLENBQUMsRUFBRSxjQUFjLEVBQUUsa0JBQWtCLEVBQUUsQ0FBQztvQkFDakUsWUFBWSxFQUFFLEVBQUU7b0JBQ2hCLFVBQVUsRUFBRSxHQUFHO29CQUNmLElBQUksRUFBRSxTQUFTO2lCQUNoQjtnQkFDRCxrQkFBa0IsRUFBRSxJQUFJLG9CQUFZLENBQUMsRUFBRSxjQUFjLEVBQUUsa0JBQWtCLEVBQUUsQ0FBQzthQUM3RTtTQUNGLENBQUM7UUFDRixTQUFTLENBQUMsT0FBTyxDQUNmLENBQUMsRUFDQyxJQUFJLEVBQ0osVUFBVSxFQUNWLFlBQVksRUFDWixNQUFNLEVBQ04sV0FBVyxFQUNYLGNBQWMsRUFDZCxrQkFBa0IsRUFDbEIsS0FBSyxHQUNOLEVBQUUsRUFBRTtZQUNILEVBQUUsQ0FBQyxJQUFJLEVBQUUsS0FBSyxJQUFJLEVBQUU7Z0JBQ2xCLE1BQU0sU0FBUyxHQUFHLElBQUksbUJBQVMsQ0FBQyxVQUFVLENBQUMsQ0FBQztnQkFDNUMsSUFBSTtxQkFDRCxNQUFNLENBQ0wsaUdBQWlHLENBQ2xHO3FCQUNBLEtBQUssQ0FBQyxNQUFNLEVBQUUsWUFBWSxFQUFFLGtCQUFrQixDQUFDLENBQUM7Z0JBQ25ELE1BQU0sTUFBTSxHQUFHLE1BQU0sU0FBUyxDQUFDLDJCQUEyQixDQUFDLEtBQUssRUFBRSxXQUFXLENBQUMsQ0FBQztnQkFDL0UsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDLE9BQU8sQ0FBQyxjQUFjLENBQUMsQ0FBQztZQUN6QyxDQUFDLENBQUMsQ0FBQztRQUNMLENBQUMsQ0FDRixDQUFDO0lBQ0osQ0FBQyxDQUFDLENBQUM7SUFFSCxRQUFRLENBQUMsa0RBQWtELEVBQUUsR0FBRyxFQUFFO1FBQ2hFLE1BQU0sU0FBUyxHQUFHO1lBQ2hCO2dCQUNFLElBQUksRUFBRSxpRkFBaUY7Z0JBQ3ZGLFVBQVUsRUFBRTtvQkFDVixXQUFXLEVBQUUsb0JBQW9CO29CQUNqQyxVQUFVLEVBQUUsV0FBVztvQkFDdkIsY0FBYyxFQUFFLHNCQUFzQ