medusa-product-helper 0.0.13 → 0.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.medusa/server/src/api/store/product-helper/products/route.js +76 -0
- package/.medusa/server/src/api/store/product-helper/products/validators.js +2 -1
- package/.medusa/server/src/config/product-helper-options.js +15 -1
- package/.medusa/server/src/index.js +101 -0
- package/.medusa/server/src/providers/filter-providers/availability-provider.js +96 -0
- package/.medusa/server/src/providers/filter-providers/base-filter-provider.js +32 -0
- package/.medusa/server/src/providers/filter-providers/base-product-provider.js +122 -0
- package/.medusa/server/src/providers/filter-providers/category-provider.js +55 -0
- package/.medusa/server/src/providers/filter-providers/collection-provider.js +53 -0
- package/.medusa/server/src/providers/filter-providers/index.js +94 -0
- package/.medusa/server/src/providers/filter-providers/metadata-provider.js +88 -0
- package/.medusa/server/src/providers/filter-providers/price-range-provider.js +108 -0
- package/.medusa/server/src/providers/filter-providers/promotion-provider.js +197 -0
- package/.medusa/server/src/providers/filter-providers/promotion-window-provider.js +125 -0
- package/.medusa/server/src/providers/filter-providers/rating-provider.js +92 -0
- package/.medusa/server/src/services/dynamic-filter-service.js +814 -0
- package/.medusa/server/src/services/filter-provider-loader.js +155 -0
- package/.medusa/server/src/services/filter-provider-registry.js +142 -0
- package/.medusa/server/src/services/product-filter-service.js +230 -0
- package/.medusa/server/src/utils/query-parser.js +103 -0
- package/README.md +89 -0
- package/package.json +3 -3
|
@@ -0,0 +1,814 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DynamicFilterService = void 0;
|
|
4
|
+
const utils_1 = require("@medusajs/framework/utils");
|
|
5
|
+
const filter_provider_registry_1 = require("./filter-provider-registry");
|
|
6
|
+
/**
|
|
7
|
+
* Service that orchestrates filter application using filter providers.
|
|
8
|
+
*
|
|
9
|
+
* This service coordinates between filter providers and Medusa's RemoteQuery
|
|
10
|
+
* to build and execute product queries with dynamic filters. It handles:
|
|
11
|
+
* - Resolving providers for filter identifiers
|
|
12
|
+
* - Validating filter values
|
|
13
|
+
* - Applying filters to build query filters
|
|
14
|
+
* - Executing queries with pagination and sorting
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* const service = new DynamicFilterService(container)
|
|
19
|
+
* const result = await service.applyFilters({
|
|
20
|
+
* filterParams: {
|
|
21
|
+
* category_id: ["cat_123"],
|
|
22
|
+
* price_range: { min: 10, max: 100 },
|
|
23
|
+
* },
|
|
24
|
+
* options: pluginOptions,
|
|
25
|
+
* pagination: { limit: 20, offset: 0 },
|
|
26
|
+
* })
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
class DynamicFilterService {
|
|
30
|
+
constructor(container) {
|
|
31
|
+
// Try to resolve registry, create if not found
|
|
32
|
+
try {
|
|
33
|
+
this.registry = container.resolve("filterProviderRegistry");
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Registry not registered, create new one and register built-in providers
|
|
37
|
+
// This allows the service to work even if registry wasn't pre-registered
|
|
38
|
+
this.registry = new filter_provider_registry_1.FilterProviderRegistry();
|
|
39
|
+
// Auto-register built-in providers
|
|
40
|
+
try {
|
|
41
|
+
const { registerBuiltInProviders } = require("../providers/filter-providers");
|
|
42
|
+
registerBuiltInProviders(this.registry);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
// If registration fails, log but continue
|
|
46
|
+
console.warn("[DynamicFilterService] Failed to auto-register built-in providers:", error instanceof Error ? error.message : String(error));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
this.query = container.resolve(utils_1.ContainerRegistrationKeys.QUERY);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get the filter provider registry.
|
|
53
|
+
* Useful for registering providers programmatically.
|
|
54
|
+
*/
|
|
55
|
+
getRegistry() {
|
|
56
|
+
return this.registry;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Apply filters to a product query and return results.
|
|
60
|
+
*
|
|
61
|
+
* This method:
|
|
62
|
+
* 1. Iterates through filter parameters
|
|
63
|
+
* 2. Resolves providers for each filter identifier
|
|
64
|
+
* 3. Validates filter values (if provider has validate method)
|
|
65
|
+
* 4. Applies filters to build the query filters object
|
|
66
|
+
* 5. Executes the query with pagination and sorting
|
|
67
|
+
* 6. Returns products and count
|
|
68
|
+
*
|
|
69
|
+
* @param options - Options for applying filters
|
|
70
|
+
* @returns Products and count matching the filters
|
|
71
|
+
* @throws Error if provider validation fails or provider application fails (fail-fast)
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* const result = await service.applyFilters({
|
|
76
|
+
* filterParams: {
|
|
77
|
+
* category_id: ["cat_123", "cat_456"],
|
|
78
|
+
* price_range: { min: 10, max: 100 },
|
|
79
|
+
* },
|
|
80
|
+
* options: pluginOptions,
|
|
81
|
+
* pagination: { limit: 20, offset: 0 },
|
|
82
|
+
* projection: { fields: ["id", "title", "handle"] },
|
|
83
|
+
* })
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
async applyFilters(options) {
|
|
87
|
+
const { filterParams, options: pluginOptions, pagination, projection, context = {}, } = options;
|
|
88
|
+
// Build filter context
|
|
89
|
+
const filterContext = {
|
|
90
|
+
...context,
|
|
91
|
+
options: pluginOptions,
|
|
92
|
+
};
|
|
93
|
+
// Start with empty filters object
|
|
94
|
+
let queryFilters = {};
|
|
95
|
+
let priceRangeFilter;
|
|
96
|
+
let promotionFilter;
|
|
97
|
+
// Apply each filter using its provider
|
|
98
|
+
for (const [identifier, value] of Object.entries(filterParams)) {
|
|
99
|
+
// Skip undefined/null values
|
|
100
|
+
if (value === undefined || value === null) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const provider = this.registry.get(identifier);
|
|
104
|
+
if (!provider) {
|
|
105
|
+
// Log warning but don't fail - allow unknown filters to be ignored
|
|
106
|
+
// This enables custom providers to be added without breaking existing code
|
|
107
|
+
console.warn(`[DynamicFilterService] No filter provider found for identifier: "${identifier}". ` +
|
|
108
|
+
`Filter will be ignored. Available providers: ${this.registry.getIdentifiers().join(", ")}`);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
// Validate if provider has validation method
|
|
113
|
+
if (provider.validate) {
|
|
114
|
+
const validationResult = provider.validate(value);
|
|
115
|
+
if (validationResult && Array.isArray(validationResult)) {
|
|
116
|
+
// Provider returned validation errors
|
|
117
|
+
const errors = validationResult
|
|
118
|
+
.map((err) => `${err.path ? `${err.path}: ` : ""}${err.message}`)
|
|
119
|
+
.join(", ");
|
|
120
|
+
throw new Error(`Filter validation failed for "${identifier}": ${errors}`);
|
|
121
|
+
}
|
|
122
|
+
// If validate() throws, it will be caught below
|
|
123
|
+
}
|
|
124
|
+
// Apply filter
|
|
125
|
+
const result = provider.apply(queryFilters, value, filterContext);
|
|
126
|
+
queryFilters = await Promise.resolve(result);
|
|
127
|
+
// Extract price range filter for post-query filtering
|
|
128
|
+
// Price filtering requires post-query filtering because Medusa's query API
|
|
129
|
+
// doesn't support price range filtering directly
|
|
130
|
+
if (queryFilters.__price_range_filter__) {
|
|
131
|
+
priceRangeFilter = queryFilters.__price_range_filter__;
|
|
132
|
+
// Remove from queryFilters so it doesn't get passed to the query
|
|
133
|
+
delete queryFilters.__price_range_filter__;
|
|
134
|
+
}
|
|
135
|
+
// Extract promotion filter for post-query filtering
|
|
136
|
+
// Promotion filtering requires querying Medusa's promotion module and
|
|
137
|
+
// matching products via promotion rules/targets, which can't be done directly
|
|
138
|
+
if (queryFilters.__promotion_filter__) {
|
|
139
|
+
promotionFilter = queryFilters.__promotion_filter__;
|
|
140
|
+
// Remove from queryFilters so it doesn't get passed to the query
|
|
141
|
+
delete queryFilters.__promotion_filter__;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
// Fail-fast: throw immediately on provider errors
|
|
146
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
147
|
+
throw new Error(`Error applying filter "${identifier}": ${errorMessage}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// Build query options for Query.graph()
|
|
151
|
+
// Only include filters if they're not empty
|
|
152
|
+
// Important: Check if fields array is empty or undefined, not just truthy
|
|
153
|
+
let fields = (projection?.fields && projection.fields.length > 0)
|
|
154
|
+
? [...projection.fields]
|
|
155
|
+
: ["*"];
|
|
156
|
+
// Ensure essential relations are included to match Medusa's official format
|
|
157
|
+
// Even when using ["*"], we need to explicitly ensure relations are included
|
|
158
|
+
// Medusa's RemoteQuery may not automatically include nested relations with "*"
|
|
159
|
+
if (fields.includes("*")) {
|
|
160
|
+
// When using "*", explicitly add relations to ensure they're included
|
|
161
|
+
// This ensures variants, options, images, collection, and type are returned
|
|
162
|
+
fields = [
|
|
163
|
+
"*",
|
|
164
|
+
"variants.*",
|
|
165
|
+
"variants.prices.*",
|
|
166
|
+
"options.*",
|
|
167
|
+
"options.values.*",
|
|
168
|
+
"images.*",
|
|
169
|
+
"collection.*",
|
|
170
|
+
"type.*",
|
|
171
|
+
];
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
// If specific fields are requested, ensure essential relations are included
|
|
175
|
+
const hasVariants = fields.some(f => f.startsWith("variants"));
|
|
176
|
+
const hasOptions = fields.some(f => f.startsWith("options"));
|
|
177
|
+
const hasImages = fields.some(f => f.startsWith("images"));
|
|
178
|
+
const hasCollection = fields.includes("collection") || fields.some(f => f.startsWith("collection"));
|
|
179
|
+
const hasType = fields.includes("type") || fields.some(f => f.startsWith("type"));
|
|
180
|
+
// Add missing essential relations
|
|
181
|
+
if (!hasVariants) {
|
|
182
|
+
fields.push("variants.*", "variants.prices.*");
|
|
183
|
+
}
|
|
184
|
+
if (!hasOptions) {
|
|
185
|
+
fields.push("options.*", "options.values.*");
|
|
186
|
+
}
|
|
187
|
+
if (!hasImages)
|
|
188
|
+
fields.push("images.*");
|
|
189
|
+
if (!hasCollection)
|
|
190
|
+
fields.push("collection.*");
|
|
191
|
+
if (!hasType)
|
|
192
|
+
fields.push("type.*");
|
|
193
|
+
}
|
|
194
|
+
// Build query context for pricing (similar to Medusa's official route)
|
|
195
|
+
const queryContext = {};
|
|
196
|
+
// Add pricing context if available
|
|
197
|
+
if (context && typeof context === "object" && "pricingContext" in context) {
|
|
198
|
+
const pricingContext = context.pricingContext;
|
|
199
|
+
if (pricingContext) {
|
|
200
|
+
queryContext.variants = {
|
|
201
|
+
calculated_price: (0, utils_1.QueryContext)(pricingContext),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const queryOptions = {
|
|
206
|
+
entity: "product",
|
|
207
|
+
fields,
|
|
208
|
+
...(Object.keys(queryFilters).length > 0 && { filters: queryFilters }),
|
|
209
|
+
...(pagination && {
|
|
210
|
+
pagination: {
|
|
211
|
+
...(pagination.limit !== undefined && { take: pagination.limit }),
|
|
212
|
+
...(pagination.offset !== undefined && { skip: pagination.offset }),
|
|
213
|
+
},
|
|
214
|
+
}),
|
|
215
|
+
...(Object.keys(queryContext).length > 0 && { context: queryContext }),
|
|
216
|
+
};
|
|
217
|
+
// Debug: Log query structure when metadata filters are present
|
|
218
|
+
if (Object.keys(queryFilters).some((k) => k === "metadata")) {
|
|
219
|
+
console.log("[DynamicFilterService] Query filters:", JSON.stringify(queryFilters, null, 2));
|
|
220
|
+
console.log("[DynamicFilterService] Query fields:", fields);
|
|
221
|
+
console.log("[DynamicFilterService] Query context:", JSON.stringify(queryContext, null, 2));
|
|
222
|
+
}
|
|
223
|
+
// Execute query with pagination
|
|
224
|
+
// Note: query.graph() returns { data: Product[], metadata: { count, skip, take } }
|
|
225
|
+
const result = await this.query.graph(queryOptions);
|
|
226
|
+
// Extract products from result.data
|
|
227
|
+
// Following the pattern used in other Medusa routes (e.g., medusa-review-rating)
|
|
228
|
+
// query.graph() returns { data: Product[], metadata: { count, skip, take } }
|
|
229
|
+
const { data: productsRaw = [], metadata } = result;
|
|
230
|
+
// Ensure products is always an array and filter out null/undefined
|
|
231
|
+
let products = [];
|
|
232
|
+
if (Array.isArray(productsRaw)) {
|
|
233
|
+
products = productsRaw.filter((p) => p !== null && p !== undefined);
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
// Log unexpected structure for debugging
|
|
237
|
+
console.warn("[DynamicFilterService] Unexpected data structure from query.graph():", {
|
|
238
|
+
dataType: typeof productsRaw,
|
|
239
|
+
isArray: Array.isArray(productsRaw),
|
|
240
|
+
hasData: !!productsRaw,
|
|
241
|
+
sample: productsRaw,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
// Post-query filtering for price range
|
|
245
|
+
// This is done after the query because Medusa's query API doesn't support
|
|
246
|
+
// price range filtering directly - we need to filter based on variant prices
|
|
247
|
+
if (priceRangeFilter && products.length > 0) {
|
|
248
|
+
products = products.filter((product) => {
|
|
249
|
+
// A product matches if at least one variant has a price within the range
|
|
250
|
+
if (!product.variants || !Array.isArray(product.variants) || product.variants.length === 0) {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
// Check each variant to see if it has a price within the range
|
|
254
|
+
return product.variants.some((variant) => {
|
|
255
|
+
// Try calculated_price first (if pricing context was provided)
|
|
256
|
+
let price;
|
|
257
|
+
let currencyCode;
|
|
258
|
+
if (variant.calculated_price?.calculated_amount !== undefined) {
|
|
259
|
+
price = variant.calculated_price.calculated_amount;
|
|
260
|
+
currencyCode = variant.calculated_price.currency_code;
|
|
261
|
+
}
|
|
262
|
+
else if (variant.prices && Array.isArray(variant.prices) && variant.prices.length > 0) {
|
|
263
|
+
// Fall back to first price in prices array
|
|
264
|
+
const firstPrice = variant.prices[0];
|
|
265
|
+
price = firstPrice.amount;
|
|
266
|
+
currencyCode = firstPrice.currency_code;
|
|
267
|
+
}
|
|
268
|
+
// If no price found, exclude this variant
|
|
269
|
+
if (price === undefined) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
// If currency_code is specified in filter, check it matches
|
|
273
|
+
if (priceRangeFilter.currency_code && currencyCode !== priceRangeFilter.currency_code) {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
// Check if price is within range
|
|
277
|
+
const min = priceRangeFilter.min;
|
|
278
|
+
const max = priceRangeFilter.max;
|
|
279
|
+
if (min !== undefined && price < min) {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
if (max !== undefined && price > max) {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
return true;
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
// Update metadata count after filtering
|
|
289
|
+
if (metadata) {
|
|
290
|
+
metadata.count = products.length;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
// Post-query filtering for promotions
|
|
294
|
+
// This is done after the query because promotion-product relationships
|
|
295
|
+
// are complex and require querying the promotion module
|
|
296
|
+
if (promotionFilter && products.length > 0) {
|
|
297
|
+
try {
|
|
298
|
+
// Query all promotions (we'll filter in memory)
|
|
299
|
+
// This ensures we don't miss promotions due to query filter limitations
|
|
300
|
+
const promotionQueryOptions = {
|
|
301
|
+
entity: "promotion",
|
|
302
|
+
fields: [
|
|
303
|
+
"id",
|
|
304
|
+
"code",
|
|
305
|
+
"type",
|
|
306
|
+
"application_method.*",
|
|
307
|
+
"campaign.*",
|
|
308
|
+
"rules.*",
|
|
309
|
+
"rules.values.*",
|
|
310
|
+
"starts_at",
|
|
311
|
+
"ends_at",
|
|
312
|
+
"status",
|
|
313
|
+
],
|
|
314
|
+
};
|
|
315
|
+
// Add basic filters if specified
|
|
316
|
+
const promotionQueryFilters = {};
|
|
317
|
+
if (promotionFilter.status) {
|
|
318
|
+
promotionQueryFilters.status = promotionFilter.status;
|
|
319
|
+
}
|
|
320
|
+
if (promotionFilter.promotion_type) {
|
|
321
|
+
promotionQueryFilters.type = promotionFilter.promotion_type;
|
|
322
|
+
}
|
|
323
|
+
if (Object.keys(promotionQueryFilters).length > 0) {
|
|
324
|
+
promotionQueryOptions.filters = promotionQueryFilters;
|
|
325
|
+
}
|
|
326
|
+
const promotionResult = await this.query.graph(promotionQueryOptions);
|
|
327
|
+
let allPromotions = Array.isArray(promotionResult.data)
|
|
328
|
+
? promotionResult.data
|
|
329
|
+
: [];
|
|
330
|
+
// Debug: Log all promotions found
|
|
331
|
+
console.log(`[DynamicFilterService] Found ${allPromotions.length} total promotions`);
|
|
332
|
+
// Filter promotions by date range in memory
|
|
333
|
+
const now = new Date();
|
|
334
|
+
const promotions = allPromotions.filter((promotion) => {
|
|
335
|
+
// Filter by date range if specified
|
|
336
|
+
if (promotionFilter.starts_at || promotionFilter.ends_at) {
|
|
337
|
+
const startsAt = promotion.starts_at
|
|
338
|
+
? new Date(promotion.starts_at)
|
|
339
|
+
: null;
|
|
340
|
+
const endsAt = promotion.ends_at ? new Date(promotion.ends_at) : null;
|
|
341
|
+
if (promotionFilter.starts_at) {
|
|
342
|
+
const filterStart = new Date(promotionFilter.starts_at);
|
|
343
|
+
if (endsAt && endsAt < filterStart) {
|
|
344
|
+
return false; // Promotion ends before filter start
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
if (promotionFilter.ends_at) {
|
|
348
|
+
const filterEnd = new Date(promotionFilter.ends_at);
|
|
349
|
+
if (startsAt && startsAt > filterEnd) {
|
|
350
|
+
return false; // Promotion starts after filter end
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
// Default: only include promotions active now
|
|
356
|
+
const startsAt = promotion.starts_at
|
|
357
|
+
? new Date(promotion.starts_at)
|
|
358
|
+
: null;
|
|
359
|
+
const endsAt = promotion.ends_at ? new Date(promotion.ends_at) : null;
|
|
360
|
+
if (startsAt && startsAt > now) {
|
|
361
|
+
return false; // Promotion hasn't started yet
|
|
362
|
+
}
|
|
363
|
+
if (endsAt && endsAt < now) {
|
|
364
|
+
return false; // Promotion has ended
|
|
365
|
+
}
|
|
366
|
+
// Include open-ended promotions if include_open_ended is true
|
|
367
|
+
if (!endsAt &&
|
|
368
|
+
promotionFilter.include_open_ended === false) {
|
|
369
|
+
return false; // Exclude open-ended promotions
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return true;
|
|
373
|
+
});
|
|
374
|
+
// Debug: Log filtered promotions
|
|
375
|
+
console.log(`[DynamicFilterService] Found ${promotions.length} promotions after date filtering`);
|
|
376
|
+
if (promotions.length > 0) {
|
|
377
|
+
console.log(`[DynamicFilterService] Sample promotion:`, JSON.stringify({
|
|
378
|
+
id: promotions[0].id,
|
|
379
|
+
code: promotions[0].code,
|
|
380
|
+
type: promotions[0].type,
|
|
381
|
+
application_method: promotions[0].application_method,
|
|
382
|
+
rules_count: promotions[0].rules?.length || 0,
|
|
383
|
+
rules: promotions[0].rules,
|
|
384
|
+
}, null, 2));
|
|
385
|
+
}
|
|
386
|
+
// Initialize product tracking sets before we start processing
|
|
387
|
+
const productIdsWithPromotions = new Set();
|
|
388
|
+
const promotionDiscountMap = new Map(); // productId -> max discount percentage
|
|
389
|
+
// If promotions have no rules, try querying promotion rules separately
|
|
390
|
+
// In Medusa v2, rules might be stored as a separate entity or relationship
|
|
391
|
+
const promotionsWithoutRules = promotions.filter((p) => !p.rules || p.rules.length === 0);
|
|
392
|
+
if (promotionsWithoutRules.length > 0) {
|
|
393
|
+
console.log(`[DynamicFilterService] Found ${promotionsWithoutRules.length} promotions without rules, querying rules separately`);
|
|
394
|
+
for (const promotion of promotionsWithoutRules) {
|
|
395
|
+
try {
|
|
396
|
+
// Try querying promotion_rule entity
|
|
397
|
+
const ruleQueryResult = await this.query.graph({
|
|
398
|
+
entity: "promotion_rule",
|
|
399
|
+
fields: ["id", "attribute", "values", "operator", "promotion_id"],
|
|
400
|
+
filters: {
|
|
401
|
+
promotion_id: [promotion.id],
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
const rules = Array.isArray(ruleQueryResult.data)
|
|
405
|
+
? ruleQueryResult.data
|
|
406
|
+
: [];
|
|
407
|
+
if (rules.length > 0) {
|
|
408
|
+
console.log(`[DynamicFilterService] Found ${rules.length} rules for promotion ${promotion.id}:`, JSON.stringify(rules, null, 2));
|
|
409
|
+
// Attach rules to promotion object
|
|
410
|
+
promotion.rules = rules;
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
// Try querying with different entity names or structures
|
|
414
|
+
console.log(`[DynamicFilterService] No promotion_rule found for ${promotion.id}, trying alternative queries`);
|
|
415
|
+
// Try querying promotion_rule_value entity
|
|
416
|
+
try {
|
|
417
|
+
const ruleValueQueryResult = await this.query.graph({
|
|
418
|
+
entity: "promotion_rule_value",
|
|
419
|
+
fields: ["id", "value", "promotion_rule_id"],
|
|
420
|
+
filters: {
|
|
421
|
+
promotion_rule: {
|
|
422
|
+
promotion_id: [promotion.id],
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
});
|
|
426
|
+
const ruleValues = Array.isArray(ruleValueQueryResult.data)
|
|
427
|
+
? ruleValueQueryResult.data
|
|
428
|
+
: [];
|
|
429
|
+
if (ruleValues.length > 0) {
|
|
430
|
+
console.log(`[DynamicFilterService] Found ${ruleValues.length} rule values for promotion ${promotion.id}`);
|
|
431
|
+
// Extract product IDs from rule values
|
|
432
|
+
for (const ruleValue of ruleValues) {
|
|
433
|
+
if (ruleValue && typeof ruleValue === "object" && "value" in ruleValue) {
|
|
434
|
+
const value = ruleValue.value;
|
|
435
|
+
if (typeof value === "string") {
|
|
436
|
+
// This might be a product ID
|
|
437
|
+
productIdsWithPromotions.add(value);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
catch (error) {
|
|
444
|
+
console.log(`[DynamicFilterService] Could not query promotion_rule_value:`, error instanceof Error ? error.message : String(error));
|
|
445
|
+
}
|
|
446
|
+
// Try querying the promotion again with deeper expansion
|
|
447
|
+
try {
|
|
448
|
+
const deepPromotionQuery = await this.query.graph({
|
|
449
|
+
entity: "promotion",
|
|
450
|
+
fields: [
|
|
451
|
+
"id",
|
|
452
|
+
"rules.*",
|
|
453
|
+
"rules.values.*",
|
|
454
|
+
"rules.batch_rules.*",
|
|
455
|
+
"rules.batch_rules.values.*",
|
|
456
|
+
],
|
|
457
|
+
filters: {
|
|
458
|
+
id: [promotion.id],
|
|
459
|
+
},
|
|
460
|
+
});
|
|
461
|
+
const deepPromotions = Array.isArray(deepPromotionQuery.data)
|
|
462
|
+
? deepPromotionQuery.data
|
|
463
|
+
: [];
|
|
464
|
+
if (deepPromotions.length > 0 && deepPromotions[0].rules) {
|
|
465
|
+
console.log(`[DynamicFilterService] Found rules with deep expansion:`, JSON.stringify(deepPromotions[0].rules, null, 2));
|
|
466
|
+
promotion.rules = deepPromotions[0].rules;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
catch (error) {
|
|
470
|
+
console.log(`[DynamicFilterService] Could not query promotion with deep expansion:`, error instanceof Error ? error.message : String(error));
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
catch (error) {
|
|
475
|
+
// Entity might not exist or have different name - that's okay
|
|
476
|
+
console.log(`[DynamicFilterService] Could not query promotion_rule entity (this is normal if rules are embedded):`, error instanceof Error ? error.message : String(error));
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
// Extract product IDs from promotion rules/targets
|
|
481
|
+
// (productIdsWithPromotions and promotionDiscountMap are already declared above)
|
|
482
|
+
for (const promotion of promotions) {
|
|
483
|
+
// Check if promotion is active now (if date range not specified)
|
|
484
|
+
if (!promotionFilter.starts_at && !promotionFilter.ends_at) {
|
|
485
|
+
const startsAt = promotion.starts_at
|
|
486
|
+
? new Date(promotion.starts_at)
|
|
487
|
+
: null;
|
|
488
|
+
const endsAt = promotion.ends_at ? new Date(promotion.ends_at) : null;
|
|
489
|
+
const now = new Date();
|
|
490
|
+
if (startsAt && startsAt > now) {
|
|
491
|
+
continue; // Promotion hasn't started yet
|
|
492
|
+
}
|
|
493
|
+
if (endsAt && endsAt < now) {
|
|
494
|
+
continue; // Promotion has ended
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
// Calculate discount percentage based on application_method type
|
|
498
|
+
// In Medusa, the discount type is stored in application_method.type, not promotion.type
|
|
499
|
+
let discountPercentage;
|
|
500
|
+
const applicationMethod = promotion.application_method;
|
|
501
|
+
if (applicationMethod &&
|
|
502
|
+
typeof applicationMethod === "object" &&
|
|
503
|
+
"type" in applicationMethod) {
|
|
504
|
+
const appMethodType = applicationMethod.type;
|
|
505
|
+
if (appMethodType === "fixed" &&
|
|
506
|
+
"target_type" in applicationMethod &&
|
|
507
|
+
applicationMethod.target_type === "items") {
|
|
508
|
+
// Fixed amount discount on items (amount_off_product)
|
|
509
|
+
// Will calculate percentage per product later
|
|
510
|
+
discountPercentage = undefined;
|
|
511
|
+
}
|
|
512
|
+
else if (appMethodType === "percentage" &&
|
|
513
|
+
"target_type" in applicationMethod &&
|
|
514
|
+
applicationMethod.target_type === "items") {
|
|
515
|
+
// Percentage discount on items (percentage_off_product)
|
|
516
|
+
if ("value" in applicationMethod) {
|
|
517
|
+
discountPercentage =
|
|
518
|
+
typeof applicationMethod.value === "number"
|
|
519
|
+
? applicationMethod.value
|
|
520
|
+
: Number(applicationMethod.value);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
else if (appMethodType === "percentage" &&
|
|
524
|
+
"target_type" in applicationMethod &&
|
|
525
|
+
applicationMethod.target_type === "order") {
|
|
526
|
+
// Order-level percentage discount
|
|
527
|
+
if ("value" in applicationMethod) {
|
|
528
|
+
discountPercentage =
|
|
529
|
+
typeof applicationMethod.value === "number"
|
|
530
|
+
? applicationMethod.value
|
|
531
|
+
: Number(applicationMethod.value);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
else if (appMethodType === "free_shipping") {
|
|
535
|
+
// Free shipping - consider as 0% for product filtering
|
|
536
|
+
discountPercentage = 0;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
// Fallback: Check promotion.type for backward compatibility
|
|
540
|
+
if (discountPercentage === undefined && promotion.type) {
|
|
541
|
+
if (promotion.type === "percentage_off_product") {
|
|
542
|
+
if (applicationMethod &&
|
|
543
|
+
typeof applicationMethod === "object" &&
|
|
544
|
+
"value" in applicationMethod) {
|
|
545
|
+
discountPercentage =
|
|
546
|
+
typeof applicationMethod.value === "number"
|
|
547
|
+
? applicationMethod.value
|
|
548
|
+
: Number(applicationMethod.value);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
else if (promotion.type === "amount_off_product") {
|
|
552
|
+
// Will calculate per product
|
|
553
|
+
discountPercentage = undefined;
|
|
554
|
+
}
|
|
555
|
+
else if (promotion.type === "percentage_off_order") {
|
|
556
|
+
if (applicationMethod &&
|
|
557
|
+
typeof applicationMethod === "object" &&
|
|
558
|
+
"value" in applicationMethod) {
|
|
559
|
+
discountPercentage =
|
|
560
|
+
typeof applicationMethod.value === "number"
|
|
561
|
+
? applicationMethod.value
|
|
562
|
+
: Number(applicationMethod.value);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
// Extract product IDs from promotion rules
|
|
567
|
+
// Promotions use rules to target products (e.g., items, product_id, product_ids, collection_ids, etc.)
|
|
568
|
+
if (promotion.rules && Array.isArray(promotion.rules) && promotion.rules.length > 0) {
|
|
569
|
+
for (const rule of promotion.rules) {
|
|
570
|
+
// Check rule attribute - Medusa uses various attribute names
|
|
571
|
+
// Common ones: "items", "product_id", "product_ids", "product_collection_id", etc.
|
|
572
|
+
const ruleAttribute = rule.attribute;
|
|
573
|
+
if (ruleAttribute === "items" ||
|
|
574
|
+
ruleAttribute === "product_id" ||
|
|
575
|
+
ruleAttribute === "product_ids") {
|
|
576
|
+
// Direct product targeting
|
|
577
|
+
const productIds = Array.isArray(rule.values)
|
|
578
|
+
? rule.values
|
|
579
|
+
: rule.values
|
|
580
|
+
? [rule.values]
|
|
581
|
+
: [];
|
|
582
|
+
for (const productId of productIds) {
|
|
583
|
+
if (typeof productId === "string") {
|
|
584
|
+
productIdsWithPromotions.add(productId);
|
|
585
|
+
// Track maximum discount percentage for this product
|
|
586
|
+
if (discountPercentage !== undefined) {
|
|
587
|
+
const currentMax = promotionDiscountMap.get(productId) || 0;
|
|
588
|
+
promotionDiscountMap.set(productId, Math.max(currentMax, discountPercentage));
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
else if (ruleAttribute === "product_collection_id" ||
|
|
594
|
+
ruleAttribute === "product_collection_ids" ||
|
|
595
|
+
ruleAttribute === "collection_id" ||
|
|
596
|
+
ruleAttribute === "collection_ids") {
|
|
597
|
+
// Collection-based targeting - we'll need to get products from collections
|
|
598
|
+
// For now, we'll query products by collection_id
|
|
599
|
+
const collectionIds = Array.isArray(rule.values)
|
|
600
|
+
? rule.values
|
|
601
|
+
: rule.values
|
|
602
|
+
? [rule.values]
|
|
603
|
+
: [];
|
|
604
|
+
// Query products in these collections
|
|
605
|
+
for (const collectionId of collectionIds) {
|
|
606
|
+
if (typeof collectionId === "string") {
|
|
607
|
+
const collectionProductsResult = await this.query.graph({
|
|
608
|
+
entity: "product",
|
|
609
|
+
fields: ["id"],
|
|
610
|
+
filters: {
|
|
611
|
+
collection_id: [collectionId],
|
|
612
|
+
},
|
|
613
|
+
});
|
|
614
|
+
const collectionProducts = Array.isArray(collectionProductsResult.data)
|
|
615
|
+
? collectionProductsResult.data
|
|
616
|
+
: [];
|
|
617
|
+
for (const product of collectionProducts) {
|
|
618
|
+
if (product && typeof product === "object" && "id" in product) {
|
|
619
|
+
const productId = product.id;
|
|
620
|
+
productIdsWithPromotions.add(productId);
|
|
621
|
+
if (discountPercentage !== undefined) {
|
|
622
|
+
const currentMax = promotionDiscountMap.get(productId) || 0;
|
|
623
|
+
promotionDiscountMap.set(productId, Math.max(currentMax, discountPercentage));
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
else {
|
|
633
|
+
// No rules found - if target_type is "items", the promotion might apply to all products
|
|
634
|
+
// OR the products might be stored in a different way (e.g., join table, metadata, etc.)
|
|
635
|
+
const applicationMethod = promotion.application_method;
|
|
636
|
+
if (applicationMethod &&
|
|
637
|
+
typeof applicationMethod === "object" &&
|
|
638
|
+
"target_type" in applicationMethod &&
|
|
639
|
+
applicationMethod.target_type === "items") {
|
|
640
|
+
console.log(`[DynamicFilterService] Promotion ${promotion.id} targets items but has no rules`);
|
|
641
|
+
// Try to find products associated with this promotion through alternative methods
|
|
642
|
+
// Option 1: Check if products are stored in promotion metadata
|
|
643
|
+
if (promotion.metadata && typeof promotion.metadata === "object") {
|
|
644
|
+
const metadata = promotion.metadata;
|
|
645
|
+
if (metadata.product_ids && Array.isArray(metadata.product_ids)) {
|
|
646
|
+
console.log(`[DynamicFilterService] Found product_ids in promotion metadata`);
|
|
647
|
+
const productIds = metadata.product_ids;
|
|
648
|
+
for (const productId of productIds) {
|
|
649
|
+
if (typeof productId === "string") {
|
|
650
|
+
productIdsWithPromotions.add(productId);
|
|
651
|
+
if (discountPercentage !== undefined) {
|
|
652
|
+
const currentMax = promotionDiscountMap.get(productId) || 0;
|
|
653
|
+
promotionDiscountMap.set(productId, Math.max(currentMax, discountPercentage));
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
// Option 2: If no rules and no metadata product_ids,
|
|
660
|
+
// and the promotion is active, we might need to include all products
|
|
661
|
+
// BUT this is risky - only do it if explicitly configured
|
|
662
|
+
// For now, we'll log and skip products for promotions without rules
|
|
663
|
+
console.log(`[DynamicFilterService] Skipping products for promotion ${promotion.id} - no rules found and no product_ids in metadata`);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
// Debug: Log product IDs found with promotions
|
|
668
|
+
console.log(`[DynamicFilterService] Found ${productIdsWithPromotions.size} products with promotions`);
|
|
669
|
+
// FALLBACK: If no products found but we have promotions targeting items without rules,
|
|
670
|
+
// include all products (this is a workaround for when rules aren't queryable)
|
|
671
|
+
if (productIdsWithPromotions.size === 0 && promotions.length > 0) {
|
|
672
|
+
const itemsTargetingPromotions = promotions.filter((p) => {
|
|
673
|
+
const appMethod = p.application_method;
|
|
674
|
+
return (appMethod &&
|
|
675
|
+
typeof appMethod === "object" &&
|
|
676
|
+
"target_type" in appMethod &&
|
|
677
|
+
appMethod.target_type === "items" &&
|
|
678
|
+
(!p.rules || p.rules.length === 0));
|
|
679
|
+
});
|
|
680
|
+
if (itemsTargetingPromotions.length > 0) {
|
|
681
|
+
console.log(`[DynamicFilterService] FALLBACK: Found ${itemsTargetingPromotions.length} promotions targeting items with no rules. ` +
|
|
682
|
+
`Including all products as fallback (promotion rules may be stored in a way we can't query yet).`);
|
|
683
|
+
// Include all current products and set their discount percentages
|
|
684
|
+
for (const product of products) {
|
|
685
|
+
if (product && typeof product === "object" && "id" in product) {
|
|
686
|
+
const productId = product.id;
|
|
687
|
+
productIdsWithPromotions.add(productId);
|
|
688
|
+
// Calculate and set discount percentage for each promotion
|
|
689
|
+
for (const promo of itemsTargetingPromotions) {
|
|
690
|
+
const appMethod = promo.application_method;
|
|
691
|
+
if (appMethod &&
|
|
692
|
+
typeof appMethod === "object" &&
|
|
693
|
+
"type" in appMethod &&
|
|
694
|
+
appMethod.type === "percentage" &&
|
|
695
|
+
"value" in appMethod) {
|
|
696
|
+
const discount = typeof appMethod.value === "number"
|
|
697
|
+
? appMethod.value
|
|
698
|
+
: Number(appMethod.value);
|
|
699
|
+
if (!Number.isNaN(discount)) {
|
|
700
|
+
const currentMax = promotionDiscountMap.get(productId) || 0;
|
|
701
|
+
promotionDiscountMap.set(productId, Math.max(currentMax, discount));
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
console.log(`[DynamicFilterService] FALLBACK applied: ${productIdsWithPromotions.size} products now included`);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
if (productIdsWithPromotions.size > 0) {
|
|
711
|
+
const sampleProductIds = Array.from(productIdsWithPromotions).slice(0, 5);
|
|
712
|
+
console.log(`[DynamicFilterService] Sample product IDs with promotions:`, sampleProductIds);
|
|
713
|
+
}
|
|
714
|
+
// Filter products to only include those with matching promotions
|
|
715
|
+
products = products.filter((product) => {
|
|
716
|
+
const productId = product?.id;
|
|
717
|
+
if (!productId || typeof productId !== "string") {
|
|
718
|
+
return false;
|
|
719
|
+
}
|
|
720
|
+
// Check if product has a matching promotion
|
|
721
|
+
if (!productIdsWithPromotions.has(productId)) {
|
|
722
|
+
return false;
|
|
723
|
+
}
|
|
724
|
+
// If min_discount_percentage is specified, check discount
|
|
725
|
+
if (promotionFilter.min_discount_percentage !== undefined) {
|
|
726
|
+
const productDiscount = promotionDiscountMap.get(productId);
|
|
727
|
+
// For amount_off_product (fixed amount discount), calculate percentage from product price
|
|
728
|
+
if (productDiscount === undefined && product.variants) {
|
|
729
|
+
// Try to find promotion with fixed amount discount on items
|
|
730
|
+
// and calculate percentage from product price
|
|
731
|
+
const productPrice = product.variants?.[0]?.prices?.[0]?.amount;
|
|
732
|
+
if (productPrice && typeof productPrice === "number") {
|
|
733
|
+
// Find matching promotion with amount discount
|
|
734
|
+
for (const promotion of promotions) {
|
|
735
|
+
const applicationMethod = promotion.application_method;
|
|
736
|
+
const isAmountDiscount = (applicationMethod &&
|
|
737
|
+
typeof applicationMethod === "object" &&
|
|
738
|
+
"type" in applicationMethod &&
|
|
739
|
+
applicationMethod.type === "fixed" &&
|
|
740
|
+
"target_type" in applicationMethod &&
|
|
741
|
+
applicationMethod.target_type === "items") ||
|
|
742
|
+
promotion.type === "amount_off_product";
|
|
743
|
+
if (isAmountDiscount) {
|
|
744
|
+
if (applicationMethod &&
|
|
745
|
+
typeof applicationMethod === "object" &&
|
|
746
|
+
"value" in applicationMethod) {
|
|
747
|
+
const discountAmount = typeof applicationMethod.value === "number"
|
|
748
|
+
? applicationMethod.value
|
|
749
|
+
: Number(applicationMethod.value);
|
|
750
|
+
if (!Number.isNaN(discountAmount) && productPrice > 0) {
|
|
751
|
+
const calculatedPercentage = (discountAmount / productPrice) * 100;
|
|
752
|
+
const currentMax = promotionDiscountMap.get(productId) || 0;
|
|
753
|
+
promotionDiscountMap.set(productId, Math.max(currentMax, calculatedPercentage));
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
const finalDiscount = promotionDiscountMap.get(productId) || 0;
|
|
761
|
+
// Check if discount meets minimum threshold
|
|
762
|
+
if (finalDiscount < promotionFilter.min_discount_percentage) {
|
|
763
|
+
return false;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
return true;
|
|
767
|
+
});
|
|
768
|
+
// Update metadata count after filtering
|
|
769
|
+
if (metadata) {
|
|
770
|
+
metadata.count = products.length;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
catch (error) {
|
|
774
|
+
// Log error but don't fail - return products without promotion filtering
|
|
775
|
+
console.warn("[DynamicFilterService] Error filtering by promotions:", error instanceof Error ? error.message : String(error));
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
// Get count from metadata if available, otherwise query separately
|
|
779
|
+
let count = metadata?.count;
|
|
780
|
+
if (count === undefined) {
|
|
781
|
+
// Query for count without pagination
|
|
782
|
+
const countQueryOptions = {
|
|
783
|
+
entity: "product",
|
|
784
|
+
fields: ["id"],
|
|
785
|
+
...(Object.keys(queryFilters).length > 0 && { filters: queryFilters }),
|
|
786
|
+
};
|
|
787
|
+
const countResult = await this.query.graph(countQueryOptions);
|
|
788
|
+
// Extract count from result - following same pattern as main query
|
|
789
|
+
const { data: countData = [], metadata: countMetadata } = countResult;
|
|
790
|
+
if (countMetadata?.count !== undefined) {
|
|
791
|
+
count = countMetadata.count;
|
|
792
|
+
}
|
|
793
|
+
else if (Array.isArray(countData)) {
|
|
794
|
+
count = countData.length;
|
|
795
|
+
}
|
|
796
|
+
else {
|
|
797
|
+
// Fallback: use products array length if count query also fails
|
|
798
|
+
console.warn("[DynamicFilterService] Could not determine count from query result, using products array length");
|
|
799
|
+
count = products.length;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
return {
|
|
803
|
+
products,
|
|
804
|
+
count: typeof count === "number" ? count : products.length,
|
|
805
|
+
metadata: {
|
|
806
|
+
count: typeof count === "number" ? count : products.length,
|
|
807
|
+
skip: pagination?.offset,
|
|
808
|
+
take: pagination?.limit,
|
|
809
|
+
},
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
exports.DynamicFilterService = DynamicFilterService;
|
|
814
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZHluYW1pYy1maWx0ZXItc2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9zZXJ2aWNlcy9keW5hbWljLWZpbHRlci1zZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUNBLHFEQUFtRjtBQUVuRix5RUFBbUU7QUE2RG5FOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBc0JHO0FBQ0gsTUFBYSxvQkFBb0I7SUFJL0IsWUFBWSxTQUEwQjtRQUNwQywrQ0FBK0M7UUFDL0MsSUFBSSxDQUFDO1lBQ0gsSUFBSSxDQUFDLFFBQVEsR0FBRyxTQUFTLENBQUMsT0FBTyxDQUMvQix3QkFBd0IsQ0FDekIsQ0FBQTtRQUNILENBQUM7UUFBQyxNQUFNLENBQUM7WUFDUCwwRUFBMEU7WUFDMUUseUVBQXlFO1lBQ3pFLElBQUksQ0FBQyxRQUFRLEdBQUcsSUFBSSxpREFBc0IsRUFBRSxDQUFBO1lBRTVDLG1DQUFtQztZQUNuQyxJQUFJLENBQUM7Z0JBQ0gsTUFBTSxFQUFFLHdCQUF3QixFQUFFLEdBQUcsT0FBTyxDQUFDLCtCQUErQixDQUFDLENBQUE7Z0JBQzdFLHdCQUF3QixDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQTtZQUN6QyxDQUFDO1lBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztnQkFDZiwwQ0FBMEM7Z0JBQzFDLE9BQU8sQ0FBQyxJQUFJLENBQ1Ysb0VBQW9FLEVBQ3BFLEtBQUssWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FDdkQsQ0FBQTtZQUNILENBQUM7UUFDSCxDQUFDO1FBQ0QsSUFBSSxDQUFDLEtBQUssR0FBRyxTQUFTLENBQUMsT0FBTyxDQUFRLGlDQUF5QixDQUFDLEtBQUssQ0FBQyxDQUFBO0lBQ3hFLENBQUM7SUFFRDs7O09BR0c7SUFDSCxXQUFXO1FBQ1QsT0FBTyxJQUFJLENBQUMsUUFBUSxDQUFBO0lBQ3RCLENBQUM7SUFFRDs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O09BMkJHO0lBQ0gsS0FBSyxDQUFDLFlBQVksQ0FBQyxPQUE0QjtRQUM3QyxNQUFNLEVBQ0osWUFBWSxFQUNaLE9BQU8sRUFBRSxhQUFhLEVBQ3RCLFVBQVUsRUFDVixVQUFVLEVBQ1YsT0FBTyxHQUFHLEVBQUUsR0FDYixHQUFHLE9BQU8sQ0FBQTtRQUVYLHVCQUF1QjtRQUN2QixNQUFNLGFBQWEsR0FBa0I7WUFDbkMsR0FBRyxPQUFPO1lBQ1YsT0FBTyxFQUFFLGFBQWE7U0FDdkIsQ0FBQTtRQUVELGtDQUFrQztRQUNsQyxJQUFJLFlBQVksR0FBNEIsRUFBRSxDQUFBO1FBQzlDLElBQUksZ0JBQW9GLENBQUE7UUFDeEYsSUFBSSxlQU9TLENBQUE7UUFFYix1Q0FBdUM7UUFDdkMsS0FBSyxNQUFNLENBQUMsVUFBVSxFQUFFLEtBQUssQ0FBQyxJQUFJLE1BQU0sQ0FBQyxPQUFPLENBQUMsWUFBWSxDQUFDLEVBQUUsQ0FBQztZQUMvRCw2QkFBNkI7WUFDN0IsSUFBSSxLQUFLLEtBQUssU0FBUyxJQUFJLEtBQUssS0FBSyxJQUFJLEVBQUUsQ0FBQztnQkFDMUMsU0FBUTtZQUNWLENBQUM7WUFFRCxNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxVQUFVLENBQUMsQ0FBQTtZQUU5QyxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUM7Z0JBQ2QsbUVBQW1FO2dCQUNuRSwyRUFBMkU7Z0JBQzNFLE9BQU8sQ0FBQyxJQUFJLENBQ1Ysb0VBQW9FLFVBQVUsS0FBSztvQkFDakYsZ0RBQWdELElBQUksQ0FBQyxRQUFRLENBQUMsY0FBYyxFQUFFLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQzlGLENBQUE7Z0JBQ0QsU0FBUTtZQUNWLENBQUM7WUFFRCxJQUFJLENBQUM7Z0JBQ0gsNkNBQTZDO2dCQUM3QyxJQUFJLFFBQVEsQ0FBQyxRQUFRLEVBQUUsQ0FBQztvQkFDdEIsTUFBTSxnQkFBZ0IsR0FBRyxRQUFRLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQyxDQUFBO29CQUNqRCxJQUFJLGdCQUFnQixJQUFJLEtBQUssQ0FBQyxPQUFPLENBQUMsZ0JBQWdCLENBQUMsRUFBRSxDQUFDO3dCQUN4RCxzQ0FBc0M7d0JBQ3RDLE1BQU0sTUFBTSxHQUFHLGdCQUFnQjs2QkFDNUIsR0FBRyxDQUFDLENBQUMsR0FBRyxFQUFFLEVBQUUsQ0FBQyxHQUFHLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEdBQUcsR0FBRyxDQUFDLElBQUksSUFBSSxDQUFDLENBQUMsQ0FBQyxFQUFFLEdBQUcsR0FBRyxDQUFDLE9BQU8sRUFBRSxDQUFDOzZCQUNoRSxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUE7d0JBQ2IsTUFBTSxJQUFJLEtBQUssQ0FDYixpQ0FBaUMsVUFBVSxNQUFNLE1BQU0sRUFBRSxDQUMxRCxDQUFBO29CQUNILENBQUM7b0JBQ0QsZ0RBQWdEO2dCQUNsRCxDQUFDO2dCQUVELGVBQWU7Z0JBQ2YsTUFBTSxNQUFNLEdBQUcsUUFBUSxDQUFDLEtBQUssQ0FBQyxZQUFZLEVBQUUsS0FBSyxFQUFFLGFBQWEsQ0FBQyxDQUFBO2dCQUNqRSxZQUFZLEdBQUcsTUFBTSxPQUFPLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQUFBO2dCQUU1QyxzREFBc0Q7Z0JBQ3RELDJFQUEyRTtnQkFDM0UsaURBQWlEO2dCQUNqRCxJQUFJLFlBQVksQ0FBQyxzQkFBc0IsRUFBRSxDQUFDO29CQUN4QyxnQkFBZ0IsR0FBRyxZQUFZLENBQUMsc0JBQWdGLENBQUE7b0JBQ2hILGlFQUFpRTtvQkFDakUsT0FBTyxZQUFZLENBQUMsc0JBQXNCLENBQUE7Z0JBQzVDLENBQUM7Z0JBRUQsb0RBQW9EO2dCQUNwRCxzRUFBc0U7Z0JBQ3RFLDhFQUE4RTtnQkFDOUUsSUFBSSxZQUFZLENBQUMsb0JBQW9CLEVBQUUsQ0FBQztvQkFDdEMsZUFBZSxHQUFHLFlBQVksQ0FBQyxvQkFPOUIsQ0FBQTtvQkFDRCxpRUFBaUU7b0JBQ2pFLE9BQU8sWUFBWSxDQUFDLG9CQUFvQixDQUFBO2dCQUMxQyxDQUFDO1lBQ0gsQ0FBQztZQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7Z0JBQ2Ysa0RBQWtEO2dCQUNsRCxNQUFNLFlBQVksR0FDaEIsS0FBSyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFBO2dCQUN4RCxNQUFNLElBQUksS0FBSyxDQUNiLDBCQUEwQixVQUFVLE1BQU0sWUFBWSxFQUFFLENBQ3pELENBQUE7WUFDSCxDQUFDO1FBQ0gsQ0FBQztRQUVELHdDQUF3QztRQUN4Qyw0Q0FBNEM7UUFDNUMsMEVBQTBFO1FBQzFFLElBQUksTUFBTSxHQUFHLENBQUMsVUFBVSxFQUFFLE1BQU0sSUFBSSxVQUFVLENBQUMsTUFBTSxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUM7WUFDL0QsQ0FBQyxDQUFDLENBQUMsR0FBRyxVQUFVLENBQUMsTUFBTSxDQUFDO1lBQ3hCLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFBO1FBRVQsNEVBQTRFO1FBQzVFLDZFQUE2RTtRQUM3RSwrRUFBK0U7UUFDL0UsSUFBSSxNQUFNLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUM7WUFDekIsc0VBQXNFO1lBQ3RFLDRFQUE0RTtZQUM1RSxNQUFNLEdBQUc7Z0JBQ1AsR0FBRztnQkFDSCxZQUFZO2dCQUNaLG1CQUFtQjtnQkFDbkIsV0FBVztnQkFDWCxrQkFBa0I7Z0JBQ2xCLFVBQVU7Z0JBQ1YsY0FBYztnQkFDZCxRQUFRO2FBQ1QsQ0FBQTtRQUNILENBQUM7YUFBTSxDQUFDO1lBQ04sNEVBQTRFO1lBQzVFLE1BQU0sV0FBVyxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsVUFBVSxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUE7WUFDOUQsTUFBTSxVQUFVLEdBQUcsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxVQUFVLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQTtZQUM1RCxNQUFNLFNBQVMsR0FBRyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFBO1lBQzFELE1BQU0sYUFBYSxHQUFHLE1BQU0sQ0FBQyxRQUFRLENBQUMsWUFBWSxDQUFDLElBQUksTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxVQUFVLENBQUMsWUFBWSxDQUFDLENBQUMsQ0FBQTtZQUNuRyxNQUFNLE9BQU8sR0FBRyxNQUFNLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxJQUFJLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsVUFBVSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUE7WUFFakYsa0NBQWtDO1lBQ2xDLElBQUksQ0FBQyxXQUFXLEVBQUUsQ0FBQztnQkFDakIsTUFBTSxDQUFDLElBQUksQ0FBQyxZQUFZLEVBQUUsbUJBQW1CLENBQUMsQ0FBQTtZQUNoRCxDQUFDO1lBQ0QsSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFDO2dCQUNoQixNQUFNLENBQUMsSUFBSSxDQUFDLFdBQVcsRUFBRSxrQkFBa0IsQ0FBQyxDQUFBO1lBQzlDLENBQUM7WUFDRCxJQUFJLENBQUMsU0FBUztnQkFBRSxNQUFNLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxDQUFBO1lBQ3ZDLElBQUksQ0FBQyxhQUFhO2dCQUFFLE1BQU0sQ0FBQyxJQUFJLENBQUMsY0FBYyxDQUFDLENBQUE7WUFDL0MsSUFBSSxDQUFDLE9BQU87Z0JBQUUsTUFBTSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQTtRQUNyQyxDQUFDO1FBRUQsdUVBQXVFO1FBQ3ZFLE1BQU0sWUFBWSxHQUlkLEVBQUUsQ0FBQTtRQUVOLG1DQUFtQztRQUNuQyxJQUFJLE9BQU8sSUFBSSxPQUFPLE9BQU8sS0FBSyxRQUFRLElBQUksZ0JBQWdCLElBQUksT0FBTyxFQUFFLENBQUM7WUFDMUUsTUFBTSxjQUFjLEdBQUksT0FBd0QsQ0FBQyxjQUFjLENBQUE7WUFDL0YsSUFBSSxjQUFjLEVBQUUsQ0FBQztnQkFDbkIsWUFBWSxDQUFDLFFBQVEsR0FBRztvQkFDdEIsZ0JBQWdCLEVBQUUsSUFBQSxvQkFBWSxFQUFDLGNBQWMsQ0FBQztpQkFDL0MsQ0FBQTtZQUNILENBQUM7UUFDSCxDQUFDO1FBRUQsTUFBTSxZQUFZLEdBYWQ7WUFDRixNQUFNLEVBQUUsU0FBUztZQUNqQixNQUFNO1lBQ04sR0FBRyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsWUFBWSxDQUFDLENBQUMsTUFBTSxHQUFHLENBQUMsSUFBSSxFQUFFLE9BQU8sRUFBRSxZQUFZLEVBQUUsQ0FBQztZQUN0RSxHQUFHLENBQUMsVUFBVSxJQUFJO2dCQUNoQixVQUFVLEVBQUU7b0JBQ1YsR0FBRyxDQUFDLFVBQVUsQ0FBQyxLQUFLLEtBQUssU0FBUyxJQUFJLEVBQUUsSUFBSSxFQUFFLFVBQVUsQ0FBQyxLQUFLLEVBQUUsQ0FBQztvQkFDakUsR0FBRyxDQUFDLFVBQVUsQ0FBQyxNQUFNLEtBQUssU0FBUyxJQUFJLEVBQUUsSUFBSSxFQUFFLFVBQVUsQ0FBQyxNQUFNLEVBQUUsQ0FBQztpQkFDcEU7YUFDRixDQUFDO1lBQ0YsR0FBRyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsWUFBWSxDQUFDLENBQUMsTUFBTSxHQUFHLENBQUMsSUFBSSxFQUFFLE9BQU8sRUFBRSxZQUFZLEVBQUUsQ0FBQztTQUN2RSxDQUFBO1FBRUQsK0RBQStEO1FBQy9ELElBQUksTUFBTSxDQUFDLElBQUksQ0FBQyxZQUFZLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsS0FBSyxVQUFVLENBQUMsRUFBRSxDQUFDO1lBQzVELE9BQU8sQ0FBQyxHQUFHLENBQUMsdUNBQXVDLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxZQUFZLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUE7WUFDM0YsT0FBTyxDQUFDLEdBQUcsQ0FBQyxzQ0FBc0MsRUFBRSxNQUFNLENBQUMsQ0FBQTtZQUMzRCxPQUFPLENBQUMsR0FBRyxDQUFDLHVDQUF1QyxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsWUFBWSxFQUFFLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFBO1FBQzdGLENBQUM7UUFFRCxnQ0FBZ0M7UUFDaEMsbUZBQW1GO1FBQ25GLE1BQU0sTUFBTSxHQUFHLE1BQU0sSUFBSSxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsWUFBWSxDQUFDLENBQUE7UUFFbkQsb0NBQW9DO1FBQ3BDLGlGQUFpRjtRQUNqRiw2RUFBNkU7UUFDN0UsTUFBTSxFQUFFLElBQUksRUFBRSxXQUFXLEdBQUcsRUFBRSxFQUFFLFFBQVEsRUFBRSxHQUFHLE1BQU0sQ0FBQTtRQUVuRCxtRUFBbUU7UUFDbkUsSUFBSSxRQUFRLEdBQWMsRUFBRSxDQUFBO1FBQzVCLElBQUksS0FBSyxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUMsRUFBRSxDQUFDO1lBQy9CLFFBQVEsR0FBRyxXQUFXLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLEtBQUssSUFBSSxJQUFJLENBQUMsS0FBSyxTQUFTLENBQUMsQ0FBQTtRQUNyRSxDQUFDO2FBQU0sQ0FBQztZQUNOLHlDQUF5QztZQUN6QyxPQUFPLENBQUMsSUFBSSxDQUNWLHNFQUFzRSxFQUN0RTtnQkFDRSxRQUFRLEVBQUUsT0FBTyxXQUFXO2dCQUM1QixPQUFPLEVBQUUsS0FBSyxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUM7Z0JBQ25DLE9BQU8sRUFBRSxDQUFDLENBQUMsV0FBVztnQkFDdEIsTUFBTSxFQUFFLFdBQVc7YUFDcEIsQ0FDRixDQUFBO1FBQ0gsQ0FBQztRQUVELHVDQUF1QztRQUN2QywwRUFBMEU7UUFDMUUsNkVBQTZFO1FBQzdFLElBQUksZ0JBQWdCLElBQUksUUFBUSxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQztZQUM1QyxRQUFRLEdBQUcsUUFBUSxDQUFDLE1BQU0sQ0FBQyxDQUFDLE9BQVksRUFBRSxFQUFFO2dCQUMxQyx5RUFBeUU7Z0JBQ3pFLElBQUksQ0FBQyxPQUFPLENBQUMsUUFBUSxJQUFJLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLElBQUksT0FBTyxDQUFDLFFBQVEsQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFLENBQUM7b0JBQzNGLE9BQU8sS0FBSyxDQUFBO2dCQUNkLENBQUM7Z0JBRUQsK0RBQStEO2dCQUMvRCxPQUFPLE9BQU8sQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLENBQUMsT0FBWSxFQUFFLEVBQUU7b0JBQzVDLCtEQUErRDtvQkFDL0QsSUFBSSxLQUF5QixDQUFBO29CQUM3QixJQUFJLFlBQWdDLENBQUE7b0JBRXBDLElBQUksT0FBTyxDQUFDLGdCQUFnQixFQUFFLGlCQUFpQixLQUFLLFNBQVMsRUFBRSxDQUFDO3dCQUM5RCxLQUFLLEdBQUcsT0FBTyxDQUFDLGdCQUFnQixDQUFDLGlCQUFpQixDQUFBO3dCQUNsRCxZQUFZLEdBQUcsT0FBTyxDQUFDLGdCQUFnQixDQUFDLGFBQWEsQ0FBQTtvQkFDdkQsQ0FBQzt5QkFBTSxJQUFJLE9BQU8sQ0FBQyxNQUFNLElBQUksS0FBSyxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDLElBQUksT0FBTyxDQUFDLE1BQU0sQ0FBQyxNQUFNLEdBQUcsQ0FBQyxFQUFFLENBQUM7d0JBQ3hGLDJDQUEyQzt3QkFDM0MsTUFBTSxVQUFVLEdBQUcsT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQTt3QkFDcEMsS0FBSyxHQUFHLFVBQVUsQ0FBQyxNQUFNLENBQUE7d0JBQ3pCLFlBQVksR0FBRyxVQUFVLENBQUMsYUFBYSxDQUFBO29CQUN6QyxDQUFDO29CQUVELDBDQUEwQztvQkFDMUMsSUFBSSxLQUFLLEtBQUssU0FBUyxFQUFFLENBQUM7d0JBQ3hCLE9BQU8sS0FBSyxDQUFBO29CQUNkLENBQUM7b0JBRUQsNERBQTREO29CQUM1RCxJQUFJLGdCQUFnQixDQUFDLGFBQWEsSUFBSSxZQUFZLEtBQUssZ0JBQWdCLENBQUMsYUFBYSxFQUFFLENBQUM7d0JBQ3RGLE9BQU8sS0FBSyxDQUFBO29CQUNkLENBQUM7b0JBRUQsaUNBQWlDO29CQUNqQyxNQUFNLEdBQUcsR0FBRyxnQkFBZ0IsQ0FBQyxHQUFHLENBQUE7b0JBQ2hDLE1BQU0sR0FBRyxHQUFHLGdCQUFnQixDQUFDLEdBQUcsQ0FBQTtvQkFFaEMsSUFBSSxHQUFHLEtBQUssU0FBUyxJQUFJLEtBQUssR0FBRyxHQUFHLEVBQUUsQ0FBQzt3QkFDckMsT0FBTyxLQUFLLENBQUE7b0JBQ2QsQ0FBQztvQkFDRCxJQUFJLEdBQUcsS0FBSyxTQUFTLElBQUksS0FBSyxHQUFHLEdBQUcsRUFBRSxDQUFDO3dCQUNyQyxPQUFPLEtBQUssQ0FBQTtvQkFDZCxDQUFDO29CQUVELE9BQU8sSUFBSSxDQUFBO2dCQUNiLENBQUMsQ0FBQyxDQUFBO1lBQ0osQ0FBQyxDQUFDLENBQUE7WUFFRix3Q0FBd0M7WUFDeEMsSUFBSSxRQUFRLEVBQUUsQ0FBQztnQkFDYixRQUFRLENBQUMsS0FBSyxHQUFHLFFBQVEsQ0FBQyxNQUFNLENBQUE7WUFDbEMsQ0FBQztRQUNILENBQUM7UUFFRCxzQ0FBc0M7UUFDdEMsdUVBQXVFO1FBQ3ZFLHdEQUF3RDtRQUN4RCxJQUFJLGVBQWUsSUFBSSxRQUFRLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO1lBQzNDLElBQUksQ0FBQztnQkFDSCxnREFBZ0Q7Z0JBQ2hELHdFQUF3RTtnQkFDeEUsTUFBTSxxQkFBcUIsR0FJdkI7b0JBQ0YsTUFBTSxFQUFFLFdBQVc7b0JBQ25CLE1BQU0sRUFBRTt3QkFDTixJQUFJO3dCQUNKLE1BQU07d0JBQ04sTUFBTTt3QkFDTixzQkFBc0I7d0JBQ3RCLFlBQVk7d0JBQ1osU0FBUzt3QkFDVCxnQkFBZ0I7d0JBQ2hCLFdBQVc7d0JBQ1gsU0FBUzt3QkFDVCxRQUFRO3FCQUNUO2lCQUNGLENBQUE7Z0JBRUQsaUNBQWlDO2dCQUNqQyxNQUFNLHFCQUFxQixHQUE0QixFQUFFLENBQUE7Z0JBQ3pELElBQUksZUFBZSxDQUFDLE1BQU0sRUFBRSxDQUFDO29CQUMzQixxQkFBcUIsQ0FBQyxNQUFNLEdBQUcsZUFBZSxDQUFDLE1BQU0sQ0FBQTtnQkFDdkQsQ0FBQztnQkFDRCxJQUFJLGVBQWUsQ0FBQyxjQUFjLEVBQUUsQ0FBQztvQkFDbkMscUJBQXFCLENBQUMsSUFBSSxHQUFHLGVBQWUsQ0FBQyxjQUFjLENBQUE7Z0JBQzdELENBQUM7Z0JBQ0QsSUFBSSxNQUFNLENBQUMsSUFBSSxDQUFDLHFCQUFxQixDQUFDLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO29CQUNsRCxxQkFBcUIsQ0FBQyxPQUFPLEdBQUcscUJBQXFCLENBQUE7Z0JBQ3ZELENBQUM7Z0JBRUQsTUFBTSxlQUFlLEdBQUcsTUFBTSxJQUFJLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxxQkFBcUIsQ0FBQyxDQUFBO2dCQUNyRSxJQUFJLGFBQWEsR0FBRyxLQUFLLENBQUMsT0FBTyxDQUFDLGVBQWUsQ0FBQyxJQUFJLENBQUM7b0JBQ3JELENBQUMsQ0FBQyxlQUFlLENBQUMsSUFBSTtvQkFDdEIsQ0FBQyxDQUFDLEVBQUUsQ0FBQTtnQkFFTixrQ0FBa0M7Z0JBQ2xDLE9BQU8sQ0FBQyxHQUFHLENBQ1QsZ0NBQWdDLGFBQWEsQ0FBQyxNQUFNLG1CQUFtQixDQUN4RSxDQUFBO2dCQUVELDRDQUE0QztnQkFDNUMsTUFBTSxHQUFHLEdBQUcsSUFBSSxJQUFJLEVBQUUsQ0FBQTtnQkFDdEIsTUFBTSxVQUFVLEdBQUcsYUFBYSxDQUFDLE1BQU0sQ0FBQyxDQUFDLFNBQWMsRUFBRSxFQUFFO29CQUN6RCxvQ0FBb0M7b0JBQ3BDLElBQUksZUFBZSxDQUFDLFNBQVMsSUFBSSxlQUFlLENBQUMsT0FBTyxFQUFFLENBQUM7d0JBQ3pELE1BQU0sUUFBUSxHQUFHLFNBQVMsQ0FBQyxTQUFTOzRCQUNsQyxDQUFDLENBQUMsSUFBSSxJQUFJLENBQUMsU0FBUyxDQUFDLFNBQVMsQ0FBQzs0QkFDL0IsQ0FBQyxDQUFDLElBQUksQ0FBQTt3QkFDUixNQUFNLE1BQU0sR0FBRyxTQUFTLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxJQUFJLElBQUksQ0FBQyxTQUFTLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQTt3QkFFckUsSUFBSSxlQUFlLENBQUMsU0FBUyxFQUFFLENBQUM7NEJBQzlCLE1BQU0sV0FBVyxHQUFHLElBQUksSUFBSSxDQUFDLGVBQWUsQ0FBQyxTQUFTLENBQUMsQ0FBQTs0QkFDdkQsSUFBSSxNQUFNLElBQUksTUFBTSxHQUFHLFdBQVcsRUFBRSxDQUFDO2dDQUNuQyxPQUFPLEtBQUssQ0FBQSxDQUFDLHFDQUFxQzs0QkFDcEQsQ0FBQzt3QkFDSCxDQUFDO3dCQUVELElBQUksZUFBZSxDQUFDLE9BQU8sRUFBRSxDQUFDOzRCQUM1QixNQUFNLFNBQVMsR0FBRyxJQUFJLElBQUksQ0FBQyxlQUFlLENBQUMsT0FBTyxDQUFDLENBQUE7NEJBQ25ELElBQUksUUFBUSxJQUFJLFFBQVEsR0FBRyxTQUFTLEVBQUUsQ0FBQztnQ0FDckMsT0FBTyxLQUFLLENBQUEsQ0FBQyxvQ0FBb0M7NEJBQ25ELENBQUM7d0JBQ0gsQ0FBQztvQkFDSCxDQUFDO3lCQUFNLENBQUM7d0JBQ04sOENBQThDO3dCQUM5QyxNQUFNLFFBQVEsR0FBRyxTQUFTLENBQUMsU0FBUzs0QkFDbEMsQ0FBQyxDQUFDLElBQUksSUFBSSxDQUFDLFNBQVMsQ0FBQyxTQUFTLENBQUM7NEJBQy9CLENBQUMsQ0FBQyxJQUFJLENBQUE7d0JBQ1IsTUFBTSxNQUFNLEdBQUcsU0FBUyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsSUFBSSxJQUFJLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUE7d0JBRXJFLElBQUksUUFBUSxJQUFJLFFBQVEsR0FBRyxHQUFHLEVBQUUsQ0FBQzs0QkFDL0IsT0FBTyxLQUFLLENBQUEsQ0FBQywrQkFBK0I7d0JBQzlDLENBQUM7d0JBRUQsSUFBSSxNQUFNLElBQUksTUFBTSxHQUFHLEdBQUcsRUFBRSxDQUFDOzRCQUMzQixPQUFPLEtBQUssQ0FBQSxDQUFDLHNCQUFzQjt3QkFDckMsQ0FBQzt3QkFFRCw4REFBOEQ7d0JBQzlELElBQ0UsQ0FBQyxNQUFNOzRCQUNQLGVBQWUsQ0FBQyxrQkFBa0IsS0FBSyxLQUFLLEVBQzVDLENBQUM7NEJBQ0QsT0FBTyxLQUFLLENBQUEsQ0FBQyxnQ0FBZ0M7d0JBQy9DLENBQUM7b0JBQ0gsQ0FBQztvQkFFRCxPQUFPLElBQUksQ0FBQTtnQkFDYixDQUFDLENBQUMsQ0FBQTtnQkFFRixpQ0FBaUM7Z0JBQ2pDLE9BQU8sQ0FBQyxHQUFHLENBQ1QsZ0NBQWdDLFVBQVUsQ0FBQyxNQUFNLGtDQUFrQyxDQUNwRixDQUFBO2dCQUNELElBQUksVUFBVSxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQztvQkFDMUIsT0FBTyxDQUFDLEdBQUcsQ0FDVCwwQ0FBMEMsRUFDMUMsSUFBSSxDQUFDLFNBQVMsQ0FDWjt3QkFDRSxFQUFFLEVBQUUsVUFBVSxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUU7d0JBQ3BCLElBQUksRUFBRSxVQUFVLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSTt3QkFDeEIsSUFBSSxFQUFFLFVBQVUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJO3dCQUN4QixrQkFBa0IsRUFBRSxVQUFVLENBQUMsQ0FBQyxDQUFDLENBQUMsa0JBQWtCO3dCQUNwRCxXQUFXLEVBQUUsVUFBVSxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssRUFBRSxNQUFNLElBQUksQ0FBQzt3QkFDN0MsS0FBSyxFQUFFLFVBQVUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLO3FCQUMzQixFQUNELElBQUksRUFDSixDQUFDLENBQ0YsQ0FDRixDQUFBO2dCQUNILENBQUM7Z0JBRUQsOERBQThEO2dCQUM5RCxNQUFNLHdCQUF3QixHQUFHLElBQUksR0FBRyxFQUFVLENBQUE7Z0JBQ2xELE1BQU0sb0JBQW9CLEdBQUcsSUFBSSxHQUFHLEVBQWtCLENBQUEsQ0FBQyx1Q0FBdUM7Z0JBRTlGLHVFQUF1RTtnQkFDdkUsMkVBQTJFO2dCQUMzRSxNQUFNLHNCQUFzQixHQUFHLFVBQVUsQ0FBQyxNQUFNLENBQzlDLENBQUMsQ0FBTSxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLElBQUksQ0FBQyxDQUFDLEtBQUssQ0FBQyxNQUFNLEtBQUssQ0FBQyxDQUM3QyxDQUFBO2dCQUNELElBQUksc0JBQXNCLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO29CQUN0QyxPQUFPLENBQUMsR0FBRyxDQUNULGdDQUFnQyxzQkFBc0IsQ0FBQyxNQUFNLHNEQUFzRCxDQUNwSCxDQUFBO29CQUVELEtBQUssTUFBTSxTQUFTLElBQUksc0JBQXNCLEVBQUUsQ0FBQzt3QkFDL0MsSUFBSSxDQUFDOzRCQUNILHFDQUFxQzs0QkFDckMsTUFBTSxlQUFlLEdBQUcsTUFBTSxJQUFJLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQztnQ0FDN0MsTUFBTSxFQUFFLGdCQUFnQjtnQ0FDeEIsTUFBTSxFQUFFLENBQUMsSUFBSSxFQUFFLFdBQVcsRUFBRSxRQUFRLEVBQUUsVUFBVSxFQUFFLGNBQWMsQ0FBQztnQ0FDakUsT0FBTyxFQUFFO29DQUNQLFlBQVksRUFBRSxDQUFDLFNBQVMsQ0FBQyxFQUFFLENBQUM7aUNBQzdCOzZCQUNGLENBQUMsQ0FBQTs0QkFFRixNQUFNLEtBQUssR0FBRyxLQUFLLENBQUMsT0FBTyxDQUFDLGVBQWUsQ0FBQyxJQUFJLENBQUM7Z0NBQy9DLENBQUMsQ0FBQyxlQUFlLENBQUMsSUFBSTtnQ0FDdEIsQ0FBQyxDQUFDLEVBQUUsQ0FBQTs0QkFFTixJQUFJLEtBQUssQ0FBQyxNQUFNLEdBQUcsQ0FBQyxFQUFFLENBQUM7Z0NBQ3JCLE9BQU8sQ0FBQyxHQUFHLENBQ1QsZ0NBQWdDLEtBQUssQ0FBQyxNQUFNLHdCQUF3QixTQUFTLENBQUMsRUFBRSxHQUFHLEVBQ25GLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxFQUFFLElBQUksRUFBRSxDQUFDLENBQUMsQ0FDL0IsQ0FBQTtnQ0FDRCxtQ0FBbUM7Z0NBQ25DLFNBQVMsQ0FBQyxLQUFLLEdBQUcsS0FBSyxDQUFBOzRCQUN6QixDQUFDO2lDQUFNLENBQUM7Z0NBQ04seURBQXlEO2dDQUN6RCxPQUFPLENBQUMsR0FBRyxDQUNULHNEQUFzRCxTQUFTLENBQUMsRUFBRSw4QkFBOEIsQ0FDakcsQ0FBQTtnQ0FFRCwyQ0FBMkM7Z0NBQzNDLElBQUksQ0FBQztvQ0FDSCxNQUFNLG9CQUFvQixHQUFHLE1BQU0sSUFBSSxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUM7d0NBQ2xELE1BQU0sRUFBRSxzQkFBc0I7d0NBQzlCLE1BQU0sRUFBRSxDQUFDLElBQUksRUFBRSxPQUFPLEVBQUUsbUJBQW1CLENBQUM7d0NBQzVDLE9BQU8sRUFBRTs0Q0FDUCxjQUFjLEVBQUU7Z0RBQ2QsWUFBWSxFQUFFLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQzs2Q0FDN0I7eUNBQ0Y7cUNBQ0YsQ0FBQyxDQUFBO29DQUVGLE1BQU0sVUFBVSxHQUFHLEtBQUssQ0FBQyxPQUFPLENBQUMsb0JBQW9CLENBQUMsSUFBSSxDQUFDO3dDQUN6RCxDQUFDLENBQUMsb0JBQW9CLENBQUMsSUFBSTt3Q0FDM0IsQ0FBQyxDQUFDLEVBQUUsQ0FBQTtvQ0FFTixJQUFJLFVBQVUsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxFQUFFLENBQUM7d0NBQzFCLE9BQU8sQ0FBQyxHQUFHLENBQ1QsZ0NBQWdDLFVBQVUsQ0FBQyxNQUFNLDhCQUE4QixTQUFTLENBQUMsRUFBRSxFQUFFLENBQzlGLENBQUE7d0NBQ0QsdUNBQXVDO3dDQUN2QyxLQUFLLE1BQU0sU0FBUyxJQUFJLFVBQVUsRUFBRSxDQUFDOzRDQUNuQyxJQUFJLFNBQVMsSUFBSSxPQUFPLFNBQVMsS0FBSyxRQUFRLElBQUksT0FBTyxJQUFJLFNBQVMsRUFBRSxDQUFDO2dEQUN2RSxNQUFNLEtBQUssR0FBRyxTQUFTLENBQUMsS0FBSyxDQUFBO2dEQUM3QixJQUFJLE9BQU8sS0FBSyxLQUFLLFFBQVEsRUFBRSxDQUFDO29EQUM5Qiw2QkFBNkI7b0RBQzdCLHdCQUF3QixDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQTtnREFDckMsQ0FBQzs0Q0FDSCxDQUFDO3dDQUNILENBQUM7b0NBQ0gsQ0FBQztnQ0FDSCxDQUFDO2dDQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7b0NBQ2YsT0FBTyxDQUFDLEdBQUcsQ0FDVCw4REFBOEQsRUFDOUQsS0FBSyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUN2RCxDQUFBO2dDQUNILENBQUM7Z0NBRUQseURBQXlEO2dDQUN6RCxJQUFJLENBQUM7b0NBQ0gsTUFBTSxrQkFBa0IsR0FBRyxNQUFNLElBQUksQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDO3dDQUNoRCxNQUFNLEVBQUUsV0FBVzt3Q0FDbkIsTUFBTSxFQUFFOzRDQUNOLElBQUk7NENBQ0osU0FBUzs0Q0FDVCxnQkFBZ0I7NENBQ2hCLHFCQUFxQjs0Q0FDckIsNEJBQTRCO3lDQUM3Qjt3Q0FDRCxPQUFPLEVBQUU7NENBQ1AsRUFBRSxFQUFFLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQzt5Q0FDbkI7cUNBQ0YsQ0FBQyxDQUFBO29DQUVGLE1BQU0sY0FBYyxHQUFHLEtBQUssQ0FBQyxPQUFPLENBQUMsa0JBQWtCLENBQUMsSUFBSSxDQUFDO3dDQUMzRCxDQUFDLENBQUMsa0JBQWtCLENBQUMsSUFBSTt3Q0FDekIsQ0FBQyxDQUFDLEVBQUUsQ0FBQTtvQ0FFTixJQUFJLGNBQWMsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxJQUFJLGNBQWMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLEVBQUUsQ0FBQzt3Q0FDekQsT0FBTyxDQUFDLEdBQUcsQ0FDVCx5REFBeUQsRUFDekQsSUFBSSxDQUFDLFNBQVMsQ0FBQyxjQUFjLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxFQUFFLElBQUksRUFBRSxDQUFDLENBQUMsQ0FDakQsQ0FBQTt3Q0FDRCxTQUFTLENBQUMsS0FBSyxHQUFHLGNBQWMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUE7b0NBQzNDLENBQUM7Z0NBQ0gsQ0FBQztnQ0FBQyxPQUFPLEtBQUssRUFBRSxDQUFDO29DQUNmLE9BQU8sQ0FBQyxHQUFHLENBQ1QsdUVBQXVFLEVBQ3ZFLEtBQUssWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FDdkQsQ0FBQTtnQ0FDSCxDQUFDOzRCQUNILENBQUM7d0JBQ0gsQ0FBQzt3QkFBQyxPQUFPLEtBQUssRUFBRSxDQUFDOzRCQUNmLDhEQUE4RDs0QkFDOUQsT0FBTyxDQUFDLEdBQUcsQ0FDVCxzR0FBc0csRUFDdEcsS0FBSyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUN2RCxDQUFBO3dCQUNILENBQUM7b0JBQ0gsQ0FBQztnQkFDSCxDQUFDO2dCQUVELG1EQUFtRDtnQkFDbkQsaUZBQWlGO2dCQUVqRixLQUFLLE1BQU0sU0FBUyxJQUFJLFVBQVUsRUFBRSxDQUFDO29CQUNuQyxpRUFBaUU7b0JBQ2pFLElBQUksQ0FBQyxlQUFlLENBQUMsU0FBUyxJQUFJLENBQUMsZUFBZSxDQUFDLE9BQU8sRUFBRSxDQUFDO3dCQUMzRCxNQUFNLFFBQVEsR0FBRyxTQUFTLENBQUMsU0FBUzs0QkFDbEMsQ0FBQyxDQUFDLElBQUksSUFBSSxDQUFDLFNBQVMsQ0FBQyxTQUFTLENBQUM7NEJBQy9CLENBQUMsQ0FBQyxJQUFJLENBQUE7d0JBQ1IsTUFBTSxNQUFNLEdBQUcsU0FBUyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsSUFBSSxJQUFJLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUE7d0JBQ3JFLE1BQU0sR0FBRyxHQUFHLElBQUksSUFBSSxFQUFFLENBQUE7d0JBRXRCLElBQUksUUFBUSxJQUFJLFFBQVEsR0FBRyxHQUFHLEVBQUUsQ0FBQzs0QkFDL0IsU0FBUSxDQUFDLCtCQUErQjt3QkFDMUMsQ0FBQzt3QkFDRCxJQUFJLE1BQU0sSUFBSSxNQUFNLEdBQUcsR0FBRyxFQUFFLENBQUM7NEJBQzNCLFNBQVEsQ0FBQyxzQkFBc0I7d0JBQ2pDLENBQUM7b0JBQ0gsQ0FBQztvQkFFRCxpRUFBaUU7b0JBQ2pFLHdGQUF3RjtvQkFDeEYsSUFBSSxrQkFBc0MsQ0FBQTtvQkFFMUMsTUFBTSxpQkFBaUIsR0FBRyxTQUFTLENBQUMsa0JBQWtCLENBQUE7b0JBQ3RELElBQ0UsaUJBQWlCO3dCQUNqQixPQUFPLGlCQUFpQixLQUFLLFFBQVE7d0JBQ3JDLE1BQU0sSUFBSSxpQkFBaUIsRUFDM0IsQ0FBQzt3QkFDRCxNQUFNLGFBQWEsR0FBRyxpQkFBaUIsQ0FBQyxJQUFjLENBQUE7d0JBRXRELElBQ0UsYUFBYSxLQUFLLE9BQU87NEJBQ3pCLGFBQWEsSUFBSSxpQkFBaUI7NEJBQ2xDLGlCQUFpQixDQUFDLFdBQVcsS0FBSyxPQUFPLEVBQ3pDLENBQUM7NEJBQ0Qsc0RBQXNEOzRCQUN0RCw4Q0FBOEM7NEJBQzlDLGtCQUFrQixHQUFHLFNBQVMsQ0FBQTt3QkFDaEMsQ0FBQzs2QkFBTSxJQUNMLGFBQWEsS0FBSyxZQUFZOzRCQUM5QixhQUFhLElBQUksaUJBQWlCOzRCQUNsQyxpQkFBaUIsQ0FBQyxXQUFXLEtBQUssT0FBTyxFQUN6QyxDQUFDOzRCQUNELHdEQUF3RDs0QkFDeEQsSUFBSSxPQUFPLElBQUksaUJBQWlCLEVBQUUsQ0FBQztnQ0FDakMsa0JBQWtCO29DQUNoQixPQUFPLGlCQUFpQixDQUFDLEtBQUssS0FBSyxRQUFRO3dDQUN6QyxDQUFDLENBQUMsaUJBQWlCLENBQUMsS0FBSzt3Q0FDekIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxpQkFBaUIsQ0FBQyxLQUFLLENBQUMsQ0FBQTs0QkFDdkMsQ0FBQzt3QkFDSCxDQUFDOzZCQUFNLElBQ0wsYUFBYSxLQUFLLFlBQVk7NEJBQzlCLGFBQWEsSUFBSSxpQkFBaUI7NEJBQ2xDLGlCQUFpQixDQUFDLFdBQVcsS0FBSyxPQUFPLEVBQ3pDLENBQUM7NEJBQ0Qsa0NBQWtDOzRCQUNsQyxJQUFJLE9BQU8sSUFBSSxpQkFBaUIsRUFBRSxDQUFDO2dDQUNqQyxrQkFBa0I7b0NBQ2hCLE9BQU8saUJBQWlCLENBQUMsS0FBSyxLQUFLLFFBQVE7d0NBQ3pDLENBQUMsQ0FBQyxpQkFBaUIsQ0FBQyxLQUFLO3dDQUN6QixDQUFDLENBQUMsTUFBTSxDQUFDLGlCQUFpQixDQUFDLEtBQUssQ0FBQyxDQUFBOzRCQUN2QyxDQUFDO3dCQUNILENBQUM7NkJBQU0sSUFBSSxhQUFhLEtBQUssZUFBZSxFQUFFLENBQUM7NEJBQzdDLHVEQUF1RDs0QkFDdkQsa0JBQWtCLEdBQUcsQ0FBQyxDQUFBO3dCQUN4QixDQUFDO29CQUNILENBQUM7b0JBRUQsNERBQTREO29CQUM1RCxJQUFJLGtCQUFrQixLQUFLLFNBQVMsSUFBSSxTQUFTLENBQUMsSUFBSSxFQUFFLENBQUM7d0JBQ3ZELElBQUksU0FBUyxDQUFDLElBQUksS0FBSyx3QkFBd0IsRUFBRSxDQUFDOzRCQUNoRCxJQUNFLGlCQUFpQjtnQ0FDakIsT0FBTyxpQkFBaUIsS0FBSyxRQUFRO2dDQUNyQyxPQUFPLElBQUksaUJBQWlCLEVBQzVCLENBQUM7Z0NBQ0Qsa0JBQWtCO29DQUNoQixPQUFPLGlCQUFpQixDQUFDLEtBQUssS0FBSyxRQUFRO3dDQUN6QyxDQUFDLENBQUMsaUJBQWlCLENBQUMsS0FBSzt3Q0FDekIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxpQkFBaUIsQ0FBQyxLQUFLLENBQUMsQ0FBQTs0QkFDdkMsQ0FBQzt3QkFDSCxDQUFDOzZCQUFNLElBQUksU0FBUyxDQUFDLElBQUksS0FBSyxvQkFBb0IsRUFBRSxDQUFDOzRCQUNuRCw2QkFBNkI7NEJBQzdCLGtCQUFrQixHQUFHLFNBQVMsQ0FBQTt3QkFDaEMsQ0FBQzs2QkFBTSxJQUFJLFNBQVMsQ0FBQyxJQUFJLEtBQUssc0JBQXNCLEVBQUUsQ0FBQzs0QkFDckQsSUFDRSxpQkFBaUI7Z0NBQ2pCLE9BQU8saUJBQWlCLEtBQUssUUFBUTtnQ0FDckMsT0FBTyxJQUFJLGlCQUFpQixFQUM1QixDQUFDO2dDQUNELGtCQUFrQjtvQ0FDaEIsT0FBTyxpQkFBaUIsQ0FBQyxLQUFLLEtBQUssUUFBUTt3Q0FDekMsQ0FBQyxDQUFDLGlCQUFpQixDQUFDLEtBQUs7d0NBQ3pCLENBQUMsQ0FBQyxNQUFNLENBQUMsaUJBQWlCLENBQUMsS0FBSyxDQUFDLENBQUE7NEJBQ3ZDLENBQUM7d0JBQ0gsQ0FBQztvQkFDSCxDQUFDO29CQUVELDJDQUEyQztvQkFDM0MsdUdBQXVHO29CQUN2RyxJQUFJLFNBQVMsQ0FBQyxLQUFLLElBQUksS0FBSyxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLElBQUksU0FBUyxDQUFDLEtBQUssQ0FBQyxNQUFNLEdBQUcsQ0FBQyxFQUFFLENBQUM7d0JBQ3BGLEtBQUssTUFBTSxJQUFJLElBQUksU0FBUyxDQUFDLEtBQUssRUFBRSxDQUFDOzRCQUNuQyw2REFBNkQ7NEJBQzdELG1GQUFtRjs0QkFDbkYsTUFBTSxhQUFhLEdBQUcsSUFBSSxDQUFDLFNBQW1CLENBQUE7NEJBRTlDLElBQ0UsYUFBYSxLQUFLLE9BQU87Z0NBQ3pCLGFBQWEsS0FBSyxZQUFZO2dDQUM5QixhQUFhLEtBQUssYUFBYSxFQUMvQixDQUFDO2dDQUNELDJCQUEyQjtnQ0FDM0IsTUFBTSxVQUFVLEdBQUcsS0FBSyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDO29DQUMzQyxDQUFDLENBQUMsSUFBSSxDQUFDLE1BQU07b0NBQ2IsQ0FBQyxDQUFDLElBQUksQ0FBQyxNQUFNO3dDQUNYLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUM7d0NBQ2YsQ0FBQyxDQUFDLEVBQUUsQ0FBQTtnQ0FFUixLQUFLLE1BQU0sU0FBUyxJQUFJLFVBQVUsRUFBRSxDQUFDO29DQUNuQyxJQUFJLE9BQU8sU0FBUyxLQUFLLFFBQVEsRUFBRSxDQUFDO3dDQUNsQyx3QkFBd0IsQ0FBQyxHQUFHLENBQUMsU0FBUyxDQUFDLENBQUE7d0NBRXZDLHFEQUFxRDt3Q0FDckQsSUFBSSxrQkFBa0IsS0FBSyxTQUFTLEVBQUUsQ0FBQzs0Q0FDckMsTUFBTSxVQUFVLEdBQUcsb0JBQW9CLENBQUMsR0FBRyxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsQ0FBQTs0Q0FDM0Qsb0JBQW9CLENBQUMsR0FBRyxDQUN0QixTQUFTLEVBQ1QsSUFBSSxDQUFDLEdBQUcsQ0FBQyxVQUFVLEVBQUUsa0JBQWtCLENBQUMsQ0FDekMsQ0FBQTt3Q0FDSCxDQUFDO29DQUNILENBQUM7Z0NBQ0gsQ0FBQzs0QkFDSCxDQUFDO2lDQUFNLElBQ0wsYUFBYSxLQUFLLHVCQUF1QjtnQ0FDekMsYUFBYSxLQUFLLHdCQUF3QjtnQ0FDMUMsYUFBYSxLQUFLLGVBQWU7Z0NBQ2pDLGFBQWEsS0FBSyxnQkFBZ0IsRUFDbEMsQ0FBQztnQ0FDRCwyRUFBMkU7Z0NBQzNFLGlEQUFpRDtnQ0FDakQsTUFBTSxhQUFhLEdBQUcsS0FBSyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDO29DQUM5QyxDQUFDLENBQUMsSUFBSSxDQUFDLE1BQU07b0NBQ2IsQ0FBQyxDQUFDLElBQUksQ0FBQyxNQUFNO3dDQUNYLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUM7d0NBQ2YsQ0FBQyxDQUFDLEVBQUUsQ0FBQTtnQ0FFUixzQ0FBc0M7Z0NBQ3RDLEtBQUssTUFBTSxZQUFZLElBQUksYUFBYSxFQUFFLENBQUM7b0NBQ3pDLElBQUksT0FBTyxZQUFZLEtBQUssUUFBUSxFQUFFLENBQUM7d0NBQ3JDLE1BQU0sd0JBQXdCLEdBQUcsTUFBTSxJQUFJLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQzs0Q0FDdEQsTUFBTSxFQUFFLFNBQVM7NENBQ2pCLE1BQU0sRUFBRSxDQUFDLElBQUksQ0FBQzs0Q0FDZCxPQUFPLEVBQUU7Z0RBQ1AsYUFBYSxFQUFFLENBQUMsWUFBWSxDQUFDOzZDQUM5Qjt5Q0FDRixDQUFDLENBQUE7d0NBRUYsTUFBTSxrQkFBa0IsR0FBRyxLQUFLLENBQUMsT0FBTyxDQUN0Qyx3QkFBd0IsQ0FBQyxJQUFJLENBQzlCOzRDQUNDLENBQUMsQ0FBQyx3QkFBd0IsQ0FBQyxJQUFJOzRDQUMvQixDQUFDLENBQUMsRUFBRSxDQUFBO3dDQUVOLEtBQUssTUFBTSxPQUFPLElBQUksa0JBQWtCLEVBQUUsQ0FBQzs0Q0FDekMsSUFBSSxPQUFPLElBQUksT0FBTyxPQUFPLEtBQUssUUFBUSxJQUFJLElBQUksSUFBSSxPQUFPLEVBQUUsQ0FBQztnREFDOUQsTUFBTSxTQUFTLEdBQUcsT0FBTyxDQUFDLEVBQVksQ0FBQTtnREFDdEMsd0JBQXdCLENBQUMsR0FBRyxDQUFDLFNBQVMsQ0FBQyxDQUFBO2dEQUV2QyxJQUFJLGtCQUFrQixLQUFLLFNBQVMsRUFBRSxDQUFDO29EQUNyQyxNQUFNLFVBQVUsR0FDZCxvQkFBb0IsQ0FBQyxHQUFHLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFBO29EQUMxQyxvQkFBb0IsQ0FBQyxHQUFHLENBQ3RCLFNBQVMsRUFDVCxJQUFJLENBQUMsR0FBRyxDQUFDLFVBQVUsRUFBRSxrQkFBa0IsQ0FBQyxDQUN6QyxDQUFBO2dEQUNILENBQUM7NENBQ0gsQ0FBQzt3Q0FDSCxDQUFDO29DQUNILENBQUM7Z0NBQ0gsQ0FBQzs0QkFDSCxDQUFDO3dCQUNILENBQUM7b0JBQ0gsQ0FBQzt5QkFBTSxDQUFDO3dCQUNOLHdGQUF3Rjt3QkFDeEYsd0ZBQXdGO3dCQUN4RixNQUFNLGlCQUFpQixHQUFHLFNBQVMsQ0FBQyxrQkFBa0IsQ0FBQTt3QkFDdEQsSUFDRSxpQkFBaUI7NEJBQ2pCLE9BQU8saUJBQWlCLEtBQUssUUFBUTs0QkFDckMsYUFBYSxJQUFJLGlCQUFpQjs0QkFDbEMsaUJBQWlCLENBQUMsV0FBVyxLQUFLLE9BQU8sRUFDekMsQ0FBQzs0QkFDRCxPQUFPLENBQUMsR0FBRyxDQUNULG9DQUFvQyxTQUFTLENBQUMsRUFBRSxpQ0FBaUMsQ0FDbEYsQ0FBQTs0QkFFRCxrRkFBa0Y7NEJBQ2xGLCtEQUErRDs0QkFDL0QsSUFBSSxTQUFTLENBQUMsUUFBUSxJQUFJLE9BQU8sU0FBUyxDQUFDLFFBQVEsS0FBSyxRQUFRLEVBQUUsQ0FBQztnQ0FDakUsTUFBTSxRQUFRLEdBQUcsU0FBUyxDQUFDLFFBQW1DLENBQUE7Z0NBQzlELElBQUksUUFBUSxDQUFDLFdBQVcsSUFBSSxLQUFLLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxXQUFXLENBQUMsRUFBRSxDQUFDO29DQUNoRSxPQUFPLENBQUMsR0FBRyxDQUNULGdFQUFnRSxDQUNqRSxDQUFBO29DQUNELE1BQU0sVUFBVSxHQUFHLFFBQVEsQ0FBQyxXQUF1QixDQUFBO29DQUNuRCxLQUFLLE1BQU0sU0FBUyxJQUFJLFVBQVUsRUFBRSxDQUFDO3dDQUNuQyxJQUFJLE9BQU8sU0FBUyxLQUFLLFFBQVEsRUFBRSxDQUFDOzRDQUNsQyx3QkFBd0IsQ0FBQyxHQUFHLENBQUMsU0FBUyxDQUFDLENBQUE7NENBQ3ZDLElBQUksa0JBQWtCLEtBQUssU0FBUyxFQUFFLENBQUM7Z0RBQ3JDLE1BQU0sVUFBVSxHQUFHLG9CQUFvQixDQUFDLEdBQUcsQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLENBQUE7Z0RBQzNELG9CQUFvQixDQUFDLEdBQUcsQ0FDdEIsU0FBUyxFQUNULElBQUksQ0FBQyxHQUFHLENBQUMsVUFBVSxFQUFFLGtCQUFrQixDQUFDLENBQ3pDLENBQUE7NENBQ0gsQ0FBQzt3Q0FDSCxDQUFDO29DQUNILENBQUM7Z0NBQ0gsQ0FBQzs0QkFDSCxDQUFDOzRCQUVELHNEQUFzRDs0QkFDdEQscUVBQXFFOzRCQUNyRSwwREFBMEQ7NEJBQzFELG9FQUFvRTs0QkFDcEUsT0FBTyxDQUFDLEdBQUcsQ0FDVCwwREFBMEQsU0FBUyxDQUFDLEVBQUUsa0RBQWtELENBQ3pILENBQUE7d0JBQ0gsQ0FBQztvQkFDSCxDQUFDO2dCQUNILENBQUM7Z0JBRUQsK0NBQStDO2dCQUMvQyxPQUFPLENBQUMsR0FBRyxDQUNULGdDQUFnQyx3QkFBd0IsQ0FBQyxJQUFJLDJCQUEyQixDQUN6RixDQUFBO2dCQUVELHVGQUF1RjtnQkFDdkYsOEVBQThFO2dCQUM5RSxJQUFJLHdCQUF3QixDQUFDLElBQUksS0FBSyxDQUFDLElBQUksVUFBVSxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQztvQkFDakUsTUFBTSx3QkFBd0IsR0FBRyxVQUFVLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBTSxFQUFFLEVBQUU7d0JBQzVELE1BQU0sU0FBUyxHQUFHLENBQUMsQ0FBQyxrQkFBa0IsQ0FBQTt3QkFDdEMsT0FBTyxDQUNMLFNBQVM7NEJBQ1QsT0FBTyxTQUFTLEtBQUssUUFBUTs0QkFDN0IsYUFBYSxJQUFJLFNBQVM7NEJBQzFCLFNBQVMsQ0FBQyxXQUFXLEtBQUssT0FBTzs0QkFDakMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLElBQUksQ0FBQyxDQUFDLEtBQUssQ0FBQyxNQUFNLEtBQUssQ0FBQyxDQUFDLENBQ25DLENBQUE7b0JBQ0gsQ0FBQyxDQUFDLENBQUE7b0JBRUYsSUFBSSx3QkFBd0IsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxFQUFFLENBQUM7d0JBQ3hDLE9BQU8sQ0FBQyxHQUFHLENBQ1QsMENBQTBDLHdCQUF3QixDQUFDLE1BQU0sNkNBQTZDOzRCQUN0SCxpR0FBaUcsQ0FDbEcsQ0FBQTt3QkFFRCxrRUFBa0U7d0JBQ2xFLEtBQUssTUFBTSxPQUFPLElBQUksUUFBUSxFQUFFLENBQUM7NEJBQy9CLElBQUksT0FBTyxJQUFJLE9BQU8sT0FBTyxLQUFLLFFBQVEsSUFBSSxJQUFJLElBQUksT0FBTyxFQUFFLENBQUM7Z0NBQzlELE1BQU0sU0FBUyxHQUFHLE9BQU8sQ0FBQyxFQUFZLENBQUE7Z0NBQ3RDLHdCQUF3QixDQUFDLEdBQUcsQ0FBQyxTQUFTLENBQUMsQ0FBQTtnQ0FFdkMsMkRBQTJEO2dDQUMzRCxLQUFLLE1BQU0sS0FBSyxJQUFJLHdCQUF3QixFQUFFLENBQUM7b0NBQzdDLE1BQU0sU0FBUyxHQUFHLEtBQUssQ0FBQyxrQkFBa0IsQ0FBQTtvQ0FDMUMsSUFDRSxTQUFTO3dDQUNULE9BQU8sU0FBUyxLQUFLLFFBQVE7d0NBQzdCLE1BQU0sSUFBSSxTQUFTO3dDQUNuQixTQUFTLENBQUMsSUFBSSxLQUFLLFlBQVk7d0NBQy9CLE9BQU8sSUFBSSxTQUFTLEVBQ3BCLENBQUM7d0NBQ0QsTUFBTSxRQUFRLEdBQUcsT0FBTyxTQUFTLENBQUMsS0FBSyxLQUFLLFFBQVE7NENBQ2xELENBQUMsQ0FBQyxTQUFTLENBQUMsS0FBSzs0Q0FDakIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLENBQUE7d0NBQzNCLElBQUksQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQUM7NENBQzVCLE1BQU0sVUFBVSxHQUFHLG9CQUFvQixDQUFDLEdBQUcsQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLENBQUE7NENBQzNELG9CQUFvQixDQUFDLEdBQUcsQ0FBQyxTQUFTLEVBQUUsSUFBSSxDQUFDLEdBQUcsQ0FBQyxVQUFVLEVBQUUsUUFBUSxDQUFDLENBQUMsQ0FBQTt3Q0FDckUsQ0FBQztvQ0FDSCxDQUFDO2dDQUNILENBQUM7NEJBQ0gsQ0FBQzt3QkFDSCxDQUFDO3dCQUVELE9BQU8sQ0FBQyxHQUFHLENBQ1QsNENBQTRDLHdCQUF3QixDQUFDLElBQUksd0JBQXdCLENBQ2xHLENBQUE7b0JBQ0gsQ0FBQztnQkFDSCxDQUFDO2dCQUVELElBQUksd0JBQXdCLENBQUMsSUFBSSxHQUFHLENBQUMsRUFBRSxDQUFDO29CQUN0QyxNQUFNLGdCQUFnQixHQUFHLEtBQUssQ0FBQyxJQUFJLENBQUMsd0JBQXdCLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFBO29CQUN6RSxPQUFPLENBQUMsR0FBRyxDQUNULDREQUE0RCxFQUM1RCxnQkFBZ0IsQ0FDakIsQ0FBQTtnQkFDSCxDQUFDO2dCQUVELGlFQUFpRTtnQkFDakUsUUFBUSxHQUFHLFFBQVEsQ0FBQyxNQUFNLENBQUMsQ0FBQyxPQUFZLEVBQUUsRUFBRTtvQkFDMUMsTUFBTSxTQUFTLEdBQUcsT0FBTyxFQUFFLEVBQUUsQ0FBQTtvQkFFN0IsSUFBSSxDQUFDLFNBQVMsSUFBSSxPQUFPLFNBQVMsS0FBSyxRQUFRLEVBQUUsQ0FBQzt3QkFDaEQsT0FBTyxLQUFLLENBQUE7b0JBQ2QsQ0FBQztvQkFFRCw0Q0FBNEM7b0JBQzVDLElBQUksQ0FBQyx3QkFBd0IsQ0FBQyxHQUFHLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQzt3QkFDN0MsT0FBTyxLQUFLLENBQUE7b0JBQ2QsQ0FBQztvQkFFRCwwREFBMEQ7b0JBQzFELElBQUksZUFBZSxDQUFDLHVCQUF1QixLQUFLLFNBQVMsRUFBRSxDQUFDO3dCQUMxRCxNQUFNLGVBQWUsR0FBRyxvQkFBb0IsQ0FBQyxHQUFHLENBQUMsU0FBUyxDQUFDLENBQUE7d0JBRTNELDBGQUEwRjt3QkFDMUYsSUFBSSxlQUFlLEtBQUssU0FBUyxJQUFJLE9BQU8sQ0FBQyxRQUFRLEVBQUUsQ0FBQzs0QkFDdEQsNERBQTREOzRCQUM1RCw4Q0FBOEM7NEJBQzlDLE1BQU0sWUFBWSxHQUFHLE9BQU8sQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxNQUFNLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxNQUFNLENBQUE7NEJBQy9ELElBQUksWUFBWSxJQUFJLE9BQU8sWUFBWSxLQUFLLFFBQVEsRUFBRSxDQUFDO2dDQUNyRCwrQ0FBK0M7Z0NBQy9DLEtBQUssTUFBTSxTQUFTLElBQUksVUFBVSxFQUFFLENBQUM7b0NBQ25DLE1BQU0saUJBQWlCLEdBQUcsU0FBUyxDQUFDLGtCQUFrQixDQUFBO29DQUN0RCxNQUFNLGdCQUFnQixHQUNwQixDQUFDLGlCQUFpQjt3Q0FDaEIsT0FBTyxpQkFBaUIsS0FBSyxRQUFRO3dDQUNyQyxNQUFNLElBQUksaUJBQWlCO3dDQUMzQixpQkFBaUIsQ0FBQyxJQUFJLEtBQUssT0FBTzt3Q0FDbEMsYUFBYSxJQUFJLGlCQUFpQjt3Q0FDbEMsaUJBQWlCLENBQUMsV0FBVyxLQUFLLE9BQU8sQ0FBQzt3Q0FDNUMsU0FBUyxDQUFDLElBQUksS0FBSyxvQkFBb0IsQ0FBQTtvQ0FFekMsSUFBSSxnQkFBZ0IsRUFBRSxDQUFDO3dDQUNyQixJQUNFLGlCQUFpQjs0Q0FDakIsT0FBTyxpQkFBaUIsS0FBSyxRQUFROzRDQUNyQyxPQUFPLElBQUksaUJBQWlCLEVBQzVCLENBQUM7NENBQ0QsTUFBTSxjQUFjLEdBQ2xCLE9BQU8saUJBQWlCLENBQUMsS0FBSyxLQUFLLFFBQVE7Z0RBQ3pDLENBQUMsQ0FBQyxpQkFBaUIsQ0FBQyxLQUFLO2dEQUN6QixDQUFDLENBQUMsTUFBTSxDQUFDLGlCQUFpQixDQUFDLEtBQUssQ0FBQyxDQUFBOzRDQUVyQyxJQUFJLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxjQUFjLENBQUMsSUFBSSxZQUFZLEdBQUcsQ0FBQyxFQUFFLENBQUM7Z0RBQ3RELE1BQU0sb0JBQW9CLEdBQ3hCLENBQUMsY0FBYyxHQUFHLFlBQVksQ0FBQyxHQUFHLEdBQUcsQ0FBQTtnREFDdkMsTUFBTSxVQUFVLEdBQ2Qsb0JBQW9CLENBQUMsR0FBRyxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsQ0FBQTtnREFDMUMsb0JBQW9CLENBQUMsR0FBRyxDQUN0QixTQUFTLEVBQ1QsSUFBSSxDQUFDLEdBQUcsQ0FBQyxVQUFVLEVBQUUsb0JBQW9CLENBQUMsQ0FDM0MsQ0FBQTs0Q0FDSCxDQUFDO3dDQUNILENBQUM7b0NBQ0gsQ0FBQztnQ0FDSCxDQUFDOzRCQUNILENBQUM7d0JBQ0gsQ0FBQzt3QkFFRCxNQUFNLGFBQWEsR0FBRyxvQkFBb0IsQ0FBQyxHQUFHLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFBO3dCQUU5RCw0Q0FBNEM7d0JBQzVDLElBQUksYUFBYSxHQUFHLGVBQWUsQ0FBQyx1QkFBd0IsRUFBRSxDQUFDOzRCQUM3RCxPQUFPLEtBQUssQ0FBQTt3QkFDZCxDQUFDO29CQUNILENBQUM7b0JBRUQsT0FBTyxJQUFJLENBQUE7Z0JBQ2IsQ0FBQyxDQUFDLENBQUE7Z0JBRUYsd0NBQXdDO2dCQUN4QyxJQUFJLFFBQVEsRUFBRSxDQUFDO29CQUNiLFFBQVEsQ0FBQyxLQUFLLEdBQUcsUUFBUSxDQUFDLE1BQU0sQ0FBQTtnQkFDbEMsQ0FBQztZQUNILENBQUM7WUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO2dCQUNmLHlFQUF5RTtnQkFDekUsT0FBTyxDQUFDLElBQUksQ0FDVix1REFBdUQsRUFDdkQsS0FBSyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUN2RCxDQUFBO1lBQ0gsQ0FBQztRQUNILENBQUM7UUFFRCxtRUFBbUU7UUFDbkUsSUFBSSxLQUFLLEdBQUcsUUFBUSxFQUFFLEtBQUssQ0FBQTtRQUMzQixJQUFJLEtBQUssS0FBSyxTQUFTLEVBQUUsQ0FBQztZQUN4QixxQ0FBcUM7WUFDckMsTUFBTSxpQkFBaUIsR0FJbkI7Z0JBQ0YsTUFBTSxFQUFFLFNBQVM7Z0JBQ2pCLE1BQU0sRUFBRSxDQUFDLElBQUksQ0FBQztnQkFDZCxHQUFHLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxZQUFZLENBQUMsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxJQUFJLEVBQUUsT0FBTyxFQUFFLFlBQVksRUFBRSxDQUFDO2FBQ3ZFLENBQUE7WUFDRCxNQUFNLFdBQVcsR0FBRyxNQUFNLElBQUksQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLGlCQUFpQixDQUFDLENBQUE7WUFFN0QsbUVBQW1FO1lBQ25FLE1BQU0sRUFBRSxJQUFJLEVBQUUsU0FBUyxHQUFHLEVBQUUsRUFBRSxRQUFRLEVBQUUsYUFBYSxFQUFFLEdBQUcsV0FBVyxDQUFBO1lBQ3JFLElBQUksYUFBYSxFQUFFLEtBQUssS0FBSyxTQUFTLEVBQUUsQ0FBQztnQkFDdkMsS0FBSyxHQUFHLGFBQWEsQ0FBQyxLQUFLLENBQUE7WUFDN0IsQ0FBQztpQkFBTSxJQUFJLEtBQUssQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQztnQkFDcEMsS0FBSyxHQUFHLFNBQVMsQ0FBQyxNQUFNLENBQUE7WUFDMUIsQ0FBQztpQkFBTSxDQUFDO2dCQUNOLGdFQUFnRTtnQkFDaEUsT0FBTyxDQUFDLElBQUksQ0FDVixpR0FBaUcsQ0FDbEcsQ0FBQTtnQkFDRCxLQUFLLEdBQUcsUUFBUSxDQUFDLE1BQU0sQ0FBQTtZQUN6QixDQUFDO1FBQ0gsQ0FBQztRQUVELE9BQU87WUFDTCxRQUFRO1lBQ1IsS0FBSyxFQUFFLE9BQU8sS0FBSyxLQUFLLFFBQVEsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsTUFBTTtZQUMxRCxRQUFRLEVBQUU7Z0JBQ1IsS0FBSyxFQUFFLE9BQU8sS0FBSyxLQUFLLFFBQVEsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsTUFBTTtnQkFDMUQsSUFBSSxFQUFFLFVBQVUsRUFBRSxNQUFNO2dCQUN4QixJQUFJLEVBQUUsVUFBVSxFQUFFLEtBQUs7YUFDeEI7U0FDRixDQUFBO0lBQ0gsQ0FBQztDQUNGO0FBci9CRCxvREFxL0JDIn0=
|