@fin.cx/opendata 1.4.5 → 1.5.4
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/dist_ts/00_commitinfo_data.js +3 -3
- package/dist_ts/classes.jsonldata.js +5 -4
- package/dist_ts/index.d.ts +1 -0
- package/dist_ts/index.js +2 -1
- package/dist_ts/plugins.d.ts +2 -1
- package/dist_ts/plugins.js +3 -2
- package/dist_ts/stocks/classes.stockservice.d.ts +32 -0
- package/dist_ts/stocks/classes.stockservice.js +221 -0
- package/dist_ts/stocks/index.d.ts +4 -0
- package/dist_ts/stocks/index.js +8 -0
- package/dist_ts/stocks/interfaces/provider.d.ts +30 -0
- package/dist_ts/stocks/interfaces/provider.js +2 -0
- package/dist_ts/stocks/interfaces/stockprice.d.ts +27 -0
- package/dist_ts/stocks/interfaces/stockprice.js +2 -0
- package/dist_ts/stocks/providers/provider.yahoo.d.ts +22 -0
- package/dist_ts/stocks/providers/provider.yahoo.js +139 -0
- package/npmextra.json +11 -10
- package/package.json +21 -20
- package/readme.hints.md +45 -0
- package/readme.md +539 -110
- package/readme.plan.md +193 -0
- package/ts/00_commitinfo_data.ts +2 -2
- package/ts/classes.jsonldata.ts +3 -2
- package/ts/index.ts +1 -0
- package/ts/plugins.ts +2 -0
- package/ts/stocks/classes.stockservice.ts +309 -0
- package/ts/stocks/index.ts +9 -0
- package/ts/stocks/interfaces/provider.ts +36 -0
- package/ts/stocks/interfaces/stockprice.ts +30 -0
- package/ts/stocks/providers/provider.yahoo.ts +159 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@fin.cx/opendata',
|
|
6
|
-
version: '1.
|
|
7
|
-
description: 'A TypeScript library for
|
|
6
|
+
version: '1.5.3',
|
|
7
|
+
description: 'A comprehensive TypeScript library that manages open business data for German companies by integrating MongoDB, processing JSONL bulk data, and automating browser interactions for Handelsregister data retrieval.'
|
|
8
8
|
};
|
|
9
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
9
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSxrQkFBa0I7SUFDeEIsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLHFOQUFxTjtDQUNuTyxDQUFBIn0=
|
|
@@ -50,9 +50,10 @@ export class JsonlDataProcessor {
|
|
|
50
50
|
},
|
|
51
51
|
finalFunction: async (streamToolsArg) => {
|
|
52
52
|
console.log(`finished processing ${totalRecordsCounter} records.`);
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
if (nextRest) {
|
|
54
|
+
JSON.parse(nextRest);
|
|
55
|
+
}
|
|
56
|
+
;
|
|
56
57
|
done.resolve();
|
|
57
58
|
},
|
|
58
59
|
}));
|
|
@@ -61,4 +62,4 @@ export class JsonlDataProcessor {
|
|
|
61
62
|
await done.promise;
|
|
62
63
|
}
|
|
63
64
|
}
|
|
64
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
65
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5qc29ubGRhdGEuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9jbGFzc2VzLmpzb25sZGF0YS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUN4QyxPQUFPLEtBQUssS0FBSyxNQUFNLFlBQVksQ0FBQztBQXlDcEMsTUFBTSxPQUFPLGtCQUFrQjtJQUU3QixZQUFZLGtCQUErQztRQUN6RCxJQUFJLENBQUMsZUFBZSxHQUFHLGtCQUFrQixDQUFDO0lBQzVDLENBQUM7SUFFRCw4REFBOEQ7SUFDdkQsS0FBSyxDQUFDLGtCQUFrQixDQUM3QixVQUFVLEdBQUcsK0RBQStEO1FBRTVFLE1BQU0sSUFBSSxHQUFHLE9BQU8sQ0FBQyxZQUFZLENBQUMsS0FBSyxFQUFFLENBQUM7UUFDMUMsTUFBTSxVQUFVLEdBQUcsTUFBTSxPQUFPLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQyxXQUFXLENBQUMsS0FBSyxDQUFDLHFCQUFxQixDQUFDLENBQUM7UUFDdkYsSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFDO1lBQ2hCLE1BQU0sT0FBTyxDQUFDLFNBQVMsQ0FBQyxFQUFFLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQyxxQkFBcUIsQ0FBQyxDQUFDO1FBQ3BFLENBQUM7YUFBTSxDQUFDO1FBQ1IsQ0FBQztRQUVELE1BQU0sWUFBWSxHQUFHLE1BQU0sT0FBTyxDQUFDLFlBQVksQ0FBQyxZQUFZLENBQUMsY0FBYyxDQUFDLFVBQVUsQ0FBQyxDQUFDO1FBQ3hGLE1BQU0sZUFBZSxHQUFHLE1BQU0sWUFBWSxDQUFDLDJCQUEyQixFQUFFLENBQUM7UUFDekUsSUFBSSxtQkFBbUIsR0FBRyxDQUFDLENBQUM7UUFDNUIsSUFBSSxRQUFRLEdBQVcsRUFBRSxDQUFDO1FBQzFCLGVBQWUsQ0FBQyxJQUFJLENBQ2xCLElBQUksT0FBTyxDQUFDLFdBQVcsQ0FBQyxXQUFXLENBQUM7WUFDbEMsVUFBVSxFQUFFLElBQUk7WUFDaEIsYUFBYSxFQUFFLEtBQUssRUFBRSxRQUFzQyxFQUFFLGNBQWMsRUFBRSxFQUFFO2dCQUM5RSxNQUFNLFVBQVUsR0FBRyxNQUFNLFFBQVEsQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDO2dCQUNyRCxVQUFVLENBQUMsSUFBSSxDQUNiLElBQUksT0FBTyxDQUFDLFdBQVcsQ0FBQyxXQUFXLENBQUM7b0JBQ2xDLFVBQVUsRUFBRSxJQUFJO29CQUNoQixhQUFhLEVBQUUsS0FBSyxFQUFFLFFBQWdCLEVBQUUsY0FBYyxFQUFFLEVBQUU7d0JBQ3hELE1BQU0sYUFBYSxHQUFHLFFBQVEsR0FBRyxRQUFRLENBQUMsUUFBUSxFQUFFLENBQUM7d0JBQ3JELE1BQU0sS0FBSyxHQUFHLGFBQWEsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUM7d0JBQ3hDLFFBQVEsR0FBRyxLQUFLLENBQUMsR0FBRyxFQUFFLENBQUM7d0JBQ3ZCLE9BQU8sQ0FBQyxHQUFHLENBQUMsZUFBZSxLQUFLLENBQUMsTUFBTSxXQUFXLENBQUMsQ0FBQzt3QkFDcEQsTUFBTSxtQkFBbUIsR0FBRyxJQUFJLE9BQU8sQ0FBQyxVQUFVLENBQUMsbUJBQW1CLENBQ3BFLEtBQUssRUFBRSxJQUFJLEVBQUUsRUFBRTs0QkFDYixJQUFJLEtBQVEsQ0FBQzs0QkFDYixJQUFJLENBQUMsSUFBSTtnQ0FBRSxPQUFPOzRCQUNsQixJQUFJLENBQUM7Z0NBQ0gsS0FBSyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUM7NEJBQzNCLENBQUM7NEJBQUMsT0FBTyxHQUFHLEVBQUUsQ0FBQztnQ0FDYixPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDO2dDQUNsQixNQUFNLE9BQU8sQ0FBQyxVQUFVLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQyxDQUFDOzRCQUMzQyxDQUFDOzRCQUNELElBQUksQ0FBQyxLQUFLO2dDQUFFLE9BQU87NEJBQ25CLG1CQUFtQixFQUFFLENBQUM7NEJBQ3RCLElBQUksbUJBQW1CLEdBQUcsS0FBSyxLQUFLLENBQUM7Z0NBQ25DLE9BQU8sQ0FBQyxHQUFHLENBQUMsR0FBRyxtQkFBbUIsaUJBQWlCLENBQUMsQ0FBQzs0QkFDdkQsTUFBTSxJQUFJLENBQUMsZUFBZSxDQUFDLEtBQUssQ0FBQyxDQUFDO3dCQUNwQyxDQUFDLEVBQ0QsSUFBSSxDQUNMLENBQUM7d0JBQ0YsTUFBTSxtQkFBbUIsQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUM7b0JBQzNDLENBQUM7b0JBQ0QsYUFBYSxFQUFFLEtBQUssRUFBRSxjQUFjLEVBQUUsRUFBRTt3QkFDdEMsT0FBTyxDQUFDLEdBQUcsQ0FBQyx1QkFBdUIsbUJBQW1CLFdBQVcsQ0FBQyxDQUFDO3dCQUNuRSxJQUFJLFFBQVEsRUFBRSxDQUFDOzRCQUNiLElBQUksQ0FBQyxLQUFLLENBQUMsUUFBUSxDQUFDLENBQUM7d0JBQ3ZCLENBQUM7d0JBQUEsQ0FBQzt3QkFDRixJQUFJLENBQUMsT0FBTyxFQUFFLENBQUM7b0JBQ2pCLENBQUM7aUJBQ0YsQ0FBQyxDQUNILENBQUM7WUFDSixDQUFDO1NBQ0YsQ0FBQyxDQUNILENBQUM7UUFDRixNQUFNLElBQUksQ0FBQyxPQUFPLENBQUM7SUFDckIsQ0FBQztDQUNGIn0=
|
package/dist_ts/index.d.ts
CHANGED
package/dist_ts/index.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export * from './classes.main.opendata.js';
|
|
2
|
-
|
|
2
|
+
export * from './stocks/index.js';
|
|
3
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLDRCQUE0QixDQUFDO0FBQzNDLGNBQWMsbUJBQW1CLENBQUMifQ==
|
package/dist_ts/plugins.d.ts
CHANGED
|
@@ -8,12 +8,13 @@ import * as smartbrowser from '@push.rocks/smartbrowser';
|
|
|
8
8
|
import * as smartdata from '@push.rocks/smartdata';
|
|
9
9
|
import * as smartdelay from '@push.rocks/smartdelay';
|
|
10
10
|
import * as smartfile from '@push.rocks/smartfile';
|
|
11
|
+
import * as smartlog from '@push.rocks/smartlog';
|
|
11
12
|
import * as smartpath from '@push.rocks/smartpath';
|
|
12
13
|
import * as smartpromise from '@push.rocks/smartpromise';
|
|
13
14
|
import * as smartrequest from '@push.rocks/smartrequest';
|
|
14
15
|
import * as smartstream from '@push.rocks/smartstream';
|
|
15
16
|
import * as smartunique from '@push.rocks/smartunique';
|
|
16
17
|
import * as smartxml from '@push.rocks/smartxml';
|
|
17
|
-
export { lik, qenv, smartarchive, smartarray, smartbrowser, smartdata, smartdelay, smartfile, smartpath, smartpromise, smartrequest, smartstream, smartunique, smartxml, };
|
|
18
|
+
export { lik, qenv, smartarchive, smartarray, smartbrowser, smartdata, smartdelay, smartfile, smartlog, smartpath, smartpromise, smartrequest, smartstream, smartunique, smartxml, };
|
|
18
19
|
import * as tsclass from '@tsclass/tsclass';
|
|
19
20
|
export { tsclass, };
|
package/dist_ts/plugins.js
CHANGED
|
@@ -10,14 +10,15 @@ import * as smartbrowser from '@push.rocks/smartbrowser';
|
|
|
10
10
|
import * as smartdata from '@push.rocks/smartdata';
|
|
11
11
|
import * as smartdelay from '@push.rocks/smartdelay';
|
|
12
12
|
import * as smartfile from '@push.rocks/smartfile';
|
|
13
|
+
import * as smartlog from '@push.rocks/smartlog';
|
|
13
14
|
import * as smartpath from '@push.rocks/smartpath';
|
|
14
15
|
import * as smartpromise from '@push.rocks/smartpromise';
|
|
15
16
|
import * as smartrequest from '@push.rocks/smartrequest';
|
|
16
17
|
import * as smartstream from '@push.rocks/smartstream';
|
|
17
18
|
import * as smartunique from '@push.rocks/smartunique';
|
|
18
19
|
import * as smartxml from '@push.rocks/smartxml';
|
|
19
|
-
export { lik, qenv, smartarchive, smartarray, smartbrowser, smartdata, smartdelay, smartfile, smartpath, smartpromise, smartrequest, smartstream, smartunique, smartxml, };
|
|
20
|
+
export { lik, qenv, smartarchive, smartarray, smartbrowser, smartdata, smartdelay, smartfile, smartlog, smartpath, smartpromise, smartrequest, smartstream, smartunique, smartxml, };
|
|
20
21
|
// @tsclass scope
|
|
21
22
|
import * as tsclass from '@tsclass/tsclass';
|
|
22
23
|
export { tsclass, };
|
|
23
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
24
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3BsdWdpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsb0JBQW9CO0FBQ3BCLE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBRTdCLE9BQU8sRUFDTCxJQUFJLEdBQ0wsQ0FBQTtBQUVELG9CQUFvQjtBQUNwQixPQUFPLEtBQUssR0FBRyxNQUFNLGlCQUFpQixDQUFDO0FBQ3ZDLE9BQU8sS0FBSyxJQUFJLE1BQU0sa0JBQWtCLENBQUM7QUFDekMsT0FBTyxLQUFLLFlBQVksTUFBTSwwQkFBMEIsQ0FBQztBQUN6RCxPQUFPLEtBQUssVUFBVSxNQUFNLHdCQUF3QixDQUFDO0FBQ3JELE9BQU8sS0FBSyxZQUFZLE1BQU0sMEJBQTBCLENBQUM7QUFDekQsT0FBTyxLQUFLLFNBQVMsTUFBTSx1QkFBdUIsQ0FBQztBQUNuRCxPQUFPLEtBQUssVUFBVSxNQUFNLHdCQUF3QixDQUFDO0FBQ3JELE9BQU8sS0FBSyxTQUFTLE1BQU0sdUJBQXVCLENBQUM7QUFDbkQsT0FBTyxLQUFLLFFBQVEsTUFBTSxzQkFBc0IsQ0FBQztBQUNqRCxPQUFPLEtBQUssU0FBUyxNQUFNLHVCQUF1QixDQUFDO0FBQ25ELE9BQU8sS0FBSyxZQUFZLE1BQU0sMEJBQTBCLENBQUM7QUFDekQsT0FBTyxLQUFLLFlBQVksTUFBTSwwQkFBMEIsQ0FBQztBQUN6RCxPQUFPLEtBQUssV0FBVyxNQUFNLHlCQUF5QixDQUFDO0FBQ3ZELE9BQU8sS0FBSyxXQUFXLE1BQU0seUJBQXlCLENBQUM7QUFDdkQsT0FBTyxLQUFLLFFBQVEsTUFBTSxzQkFBc0IsQ0FBQztBQUVqRCxPQUFPLEVBQ0wsR0FBRyxFQUNILElBQUksRUFDSixZQUFZLEVBQ1osVUFBVSxFQUNWLFlBQVksRUFDWixTQUFTLEVBQ1QsVUFBVSxFQUNWLFNBQVMsRUFDVCxRQUFRLEVBQ1IsU0FBUyxFQUNULFlBQVksRUFDWixZQUFZLEVBQ1osV0FBVyxFQUNYLFdBQVcsRUFDWCxRQUFRLEdBQ1QsQ0FBQTtBQUVELGlCQUFpQjtBQUNqQixPQUFPLEtBQUssT0FBTyxNQUFNLGtCQUFrQixDQUFDO0FBRTVDLE9BQU8sRUFDTCxPQUFPLEdBQ1IsQ0FBQSJ9
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { IStockProvider, IProviderConfig, IProviderRegistry } from './interfaces/provider.js';
|
|
2
|
+
import type { IStockPrice, IStockQuoteRequest, IStockBatchQuoteRequest } from './interfaces/stockprice.js';
|
|
3
|
+
export declare class StockPriceService implements IProviderRegistry {
|
|
4
|
+
private providers;
|
|
5
|
+
private cache;
|
|
6
|
+
private logger;
|
|
7
|
+
private cacheConfig;
|
|
8
|
+
constructor(cacheConfig?: {
|
|
9
|
+
ttl?: number;
|
|
10
|
+
maxEntries?: number;
|
|
11
|
+
});
|
|
12
|
+
register(provider: IStockProvider, config?: IProviderConfig): void;
|
|
13
|
+
unregister(providerName: string): void;
|
|
14
|
+
getProvider(name: string): IStockProvider | undefined;
|
|
15
|
+
getAllProviders(): IStockProvider[];
|
|
16
|
+
getEnabledProviders(): IStockProvider[];
|
|
17
|
+
getPrice(request: IStockQuoteRequest): Promise<IStockPrice>;
|
|
18
|
+
getPrices(request: IStockBatchQuoteRequest): Promise<IStockPrice[]>;
|
|
19
|
+
checkProvidersHealth(): Promise<Map<string, boolean>>;
|
|
20
|
+
getProviderStats(): Map<string, {
|
|
21
|
+
successCount: number;
|
|
22
|
+
errorCount: number;
|
|
23
|
+
lastError?: string;
|
|
24
|
+
lastErrorTime?: Date;
|
|
25
|
+
}>;
|
|
26
|
+
clearCache(): void;
|
|
27
|
+
setCacheTTL(ttl: number): void;
|
|
28
|
+
private fetchWithRetry;
|
|
29
|
+
private getCacheKey;
|
|
30
|
+
private getFromCache;
|
|
31
|
+
private addToCache;
|
|
32
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
export class StockPriceService {
|
|
3
|
+
constructor(cacheConfig) {
|
|
4
|
+
this.providers = new Map();
|
|
5
|
+
this.cache = new Map();
|
|
6
|
+
this.logger = console;
|
|
7
|
+
this.cacheConfig = {
|
|
8
|
+
ttl: 60000, // 60 seconds default
|
|
9
|
+
maxEntries: 1000
|
|
10
|
+
};
|
|
11
|
+
if (cacheConfig) {
|
|
12
|
+
this.cacheConfig = { ...this.cacheConfig, ...cacheConfig };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
register(provider, config) {
|
|
16
|
+
const defaultConfig = {
|
|
17
|
+
enabled: true,
|
|
18
|
+
priority: provider.priority,
|
|
19
|
+
timeout: 10000,
|
|
20
|
+
retryAttempts: 2,
|
|
21
|
+
retryDelay: 1000
|
|
22
|
+
};
|
|
23
|
+
const mergedConfig = { ...defaultConfig, ...config };
|
|
24
|
+
this.providers.set(provider.name, {
|
|
25
|
+
provider,
|
|
26
|
+
config: mergedConfig,
|
|
27
|
+
successCount: 0,
|
|
28
|
+
errorCount: 0
|
|
29
|
+
});
|
|
30
|
+
console.log(`Registered provider: ${provider.name}`);
|
|
31
|
+
}
|
|
32
|
+
unregister(providerName) {
|
|
33
|
+
this.providers.delete(providerName);
|
|
34
|
+
console.log(`Unregistered provider: ${providerName}`);
|
|
35
|
+
}
|
|
36
|
+
getProvider(name) {
|
|
37
|
+
return this.providers.get(name)?.provider;
|
|
38
|
+
}
|
|
39
|
+
getAllProviders() {
|
|
40
|
+
return Array.from(this.providers.values()).map(entry => entry.provider);
|
|
41
|
+
}
|
|
42
|
+
getEnabledProviders() {
|
|
43
|
+
return Array.from(this.providers.values())
|
|
44
|
+
.filter(entry => entry.config.enabled)
|
|
45
|
+
.sort((a, b) => (b.config.priority || 0) - (a.config.priority || 0))
|
|
46
|
+
.map(entry => entry.provider);
|
|
47
|
+
}
|
|
48
|
+
async getPrice(request) {
|
|
49
|
+
const cacheKey = this.getCacheKey(request);
|
|
50
|
+
const cached = this.getFromCache(cacheKey);
|
|
51
|
+
if (cached) {
|
|
52
|
+
console.log(`Cache hit for ${request.ticker}`);
|
|
53
|
+
return cached;
|
|
54
|
+
}
|
|
55
|
+
const providers = this.getEnabledProviders();
|
|
56
|
+
if (providers.length === 0) {
|
|
57
|
+
throw new Error('No stock price providers available');
|
|
58
|
+
}
|
|
59
|
+
let lastError;
|
|
60
|
+
for (const provider of providers) {
|
|
61
|
+
const entry = this.providers.get(provider.name);
|
|
62
|
+
try {
|
|
63
|
+
const price = await this.fetchWithRetry(() => provider.fetchPrice(request), entry.config);
|
|
64
|
+
entry.successCount++;
|
|
65
|
+
this.addToCache(cacheKey, price);
|
|
66
|
+
console.log(`Successfully fetched ${request.ticker} from ${provider.name}`);
|
|
67
|
+
return price;
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
entry.errorCount++;
|
|
71
|
+
entry.lastError = error;
|
|
72
|
+
entry.lastErrorTime = new Date();
|
|
73
|
+
lastError = error;
|
|
74
|
+
console.warn(`Provider ${provider.name} failed for ${request.ticker}: ${error.message}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
throw new Error(`Failed to fetch price for ${request.ticker} from all providers. Last error: ${lastError?.message}`);
|
|
78
|
+
}
|
|
79
|
+
async getPrices(request) {
|
|
80
|
+
const cachedPrices = [];
|
|
81
|
+
const tickersToFetch = [];
|
|
82
|
+
// Check cache for each ticker
|
|
83
|
+
for (const ticker of request.tickers) {
|
|
84
|
+
const cacheKey = this.getCacheKey({ ticker, includeExtendedHours: request.includeExtendedHours });
|
|
85
|
+
const cached = this.getFromCache(cacheKey);
|
|
86
|
+
if (cached) {
|
|
87
|
+
cachedPrices.push(cached);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
tickersToFetch.push(ticker);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (tickersToFetch.length === 0) {
|
|
94
|
+
console.log(`All ${request.tickers.length} tickers served from cache`);
|
|
95
|
+
return cachedPrices;
|
|
96
|
+
}
|
|
97
|
+
const providers = this.getEnabledProviders();
|
|
98
|
+
if (providers.length === 0) {
|
|
99
|
+
throw new Error('No stock price providers available');
|
|
100
|
+
}
|
|
101
|
+
let lastError;
|
|
102
|
+
let fetchedPrices = [];
|
|
103
|
+
for (const provider of providers) {
|
|
104
|
+
const entry = this.providers.get(provider.name);
|
|
105
|
+
try {
|
|
106
|
+
fetchedPrices = await this.fetchWithRetry(() => provider.fetchPrices({
|
|
107
|
+
tickers: tickersToFetch,
|
|
108
|
+
includeExtendedHours: request.includeExtendedHours
|
|
109
|
+
}), entry.config);
|
|
110
|
+
entry.successCount++;
|
|
111
|
+
// Cache the fetched prices
|
|
112
|
+
for (const price of fetchedPrices) {
|
|
113
|
+
const cacheKey = this.getCacheKey({
|
|
114
|
+
ticker: price.ticker,
|
|
115
|
+
includeExtendedHours: request.includeExtendedHours
|
|
116
|
+
});
|
|
117
|
+
this.addToCache(cacheKey, price);
|
|
118
|
+
}
|
|
119
|
+
console.log(`Successfully fetched ${fetchedPrices.length} prices from ${provider.name}`);
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
entry.errorCount++;
|
|
124
|
+
entry.lastError = error;
|
|
125
|
+
entry.lastErrorTime = new Date();
|
|
126
|
+
lastError = error;
|
|
127
|
+
console.warn(`Provider ${provider.name} failed for batch request: ${error.message}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (fetchedPrices.length === 0 && lastError) {
|
|
131
|
+
throw new Error(`Failed to fetch prices from all providers. Last error: ${lastError.message}`);
|
|
132
|
+
}
|
|
133
|
+
return [...cachedPrices, ...fetchedPrices];
|
|
134
|
+
}
|
|
135
|
+
async checkProvidersHealth() {
|
|
136
|
+
const health = new Map();
|
|
137
|
+
for (const [name, entry] of this.providers) {
|
|
138
|
+
if (!entry.config.enabled) {
|
|
139
|
+
health.set(name, false);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
const isAvailable = await entry.provider.isAvailable();
|
|
144
|
+
health.set(name, isAvailable);
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
health.set(name, false);
|
|
148
|
+
console.error(`Health check failed for ${name}:`, error);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return health;
|
|
152
|
+
}
|
|
153
|
+
getProviderStats() {
|
|
154
|
+
const stats = new Map();
|
|
155
|
+
for (const [name, entry] of this.providers) {
|
|
156
|
+
stats.set(name, {
|
|
157
|
+
successCount: entry.successCount,
|
|
158
|
+
errorCount: entry.errorCount,
|
|
159
|
+
lastError: entry.lastError?.message,
|
|
160
|
+
lastErrorTime: entry.lastErrorTime
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
return stats;
|
|
164
|
+
}
|
|
165
|
+
clearCache() {
|
|
166
|
+
this.cache.clear();
|
|
167
|
+
console.log('Cache cleared');
|
|
168
|
+
}
|
|
169
|
+
setCacheTTL(ttl) {
|
|
170
|
+
this.cacheConfig.ttl = ttl;
|
|
171
|
+
console.log(`Cache TTL set to ${ttl}ms`);
|
|
172
|
+
}
|
|
173
|
+
async fetchWithRetry(fetchFn, config) {
|
|
174
|
+
const maxAttempts = config.retryAttempts || 1;
|
|
175
|
+
let lastError;
|
|
176
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
177
|
+
try {
|
|
178
|
+
return await fetchFn();
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
lastError = error;
|
|
182
|
+
if (attempt < maxAttempts) {
|
|
183
|
+
const delay = (config.retryDelay || 1000) * attempt;
|
|
184
|
+
console.log(`Retry attempt ${attempt} after ${delay}ms`);
|
|
185
|
+
await plugins.smartdelay.delayFor(delay);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
throw lastError || new Error('Unknown error during fetch');
|
|
190
|
+
}
|
|
191
|
+
getCacheKey(request) {
|
|
192
|
+
return `${request.ticker}:${request.includeExtendedHours || false}`;
|
|
193
|
+
}
|
|
194
|
+
getFromCache(key) {
|
|
195
|
+
const entry = this.cache.get(key);
|
|
196
|
+
if (!entry) {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
const age = Date.now() - entry.timestamp.getTime();
|
|
200
|
+
if (age > this.cacheConfig.ttl) {
|
|
201
|
+
this.cache.delete(key);
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
return entry.price;
|
|
205
|
+
}
|
|
206
|
+
addToCache(key, price) {
|
|
207
|
+
// Enforce max entries limit
|
|
208
|
+
if (this.cache.size >= this.cacheConfig.maxEntries) {
|
|
209
|
+
// Remove oldest entry
|
|
210
|
+
const oldestKey = this.cache.keys().next().value;
|
|
211
|
+
if (oldestKey) {
|
|
212
|
+
this.cache.delete(oldestKey);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
this.cache.set(key, {
|
|
216
|
+
price,
|
|
217
|
+
timestamp: new Date()
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5zdG9ja3NlcnZpY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9zdG9ja3MvY2xhc3Nlcy5zdG9ja3NlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxlQUFlLENBQUM7QUFrQnpDLE1BQU0sT0FBTyxpQkFBaUI7SUFVNUIsWUFBWSxXQUFtRDtRQVR2RCxjQUFTLEdBQUcsSUFBSSxHQUFHLEVBQTBCLENBQUM7UUFDOUMsVUFBSyxHQUFHLElBQUksR0FBRyxFQUF1QixDQUFDO1FBQ3ZDLFdBQU0sR0FBRyxPQUFPLENBQUM7UUFFakIsZ0JBQVcsR0FBRztZQUNwQixHQUFHLEVBQUUsS0FBSyxFQUFFLHFCQUFxQjtZQUNqQyxVQUFVLEVBQUUsSUFBSTtTQUNqQixDQUFDO1FBR0EsSUFBSSxXQUFXLEVBQUUsQ0FBQztZQUNoQixJQUFJLENBQUMsV0FBVyxHQUFHLEVBQUUsR0FBRyxJQUFJLENBQUMsV0FBVyxFQUFFLEdBQUcsV0FBVyxFQUFFLENBQUM7UUFDN0QsQ0FBQztJQUNILENBQUM7SUFFTSxRQUFRLENBQUMsUUFBd0IsRUFBRSxNQUF3QjtRQUNoRSxNQUFNLGFBQWEsR0FBb0I7WUFDckMsT0FBTyxFQUFFLElBQUk7WUFDYixRQUFRLEVBQUUsUUFBUSxDQUFDLFFBQVE7WUFDM0IsT0FBTyxFQUFFLEtBQUs7WUFDZCxhQUFhLEVBQUUsQ0FBQztZQUNoQixVQUFVLEVBQUUsSUFBSTtTQUNqQixDQUFDO1FBRUYsTUFBTSxZQUFZLEdBQUcsRUFBRSxHQUFHLGFBQWEsRUFBRSxHQUFHLE1BQU0sRUFBRSxDQUFDO1FBRXJELElBQUksQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxJQUFJLEVBQUU7WUFDaEMsUUFBUTtZQUNSLE1BQU0sRUFBRSxZQUFZO1lBQ3BCLFlBQVksRUFBRSxDQUFDO1lBQ2YsVUFBVSxFQUFFLENBQUM7U0FDZCxDQUFDLENBQUM7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
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Export all interfaces
|
|
2
|
+
export * from './interfaces/stockprice.js';
|
|
3
|
+
export * from './interfaces/provider.js';
|
|
4
|
+
// Export main service
|
|
5
|
+
export * from './classes.stockservice.js';
|
|
6
|
+
// Export providers
|
|
7
|
+
export * from './providers/provider.yahoo.js';
|
|
8
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9zdG9ja3MvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsd0JBQXdCO0FBQ3hCLGNBQWMsNEJBQTRCLENBQUM7QUFDM0MsY0FBYywwQkFBMEIsQ0FBQztBQUV6QyxzQkFBc0I7QUFDdEIsY0FBYywyQkFBMkIsQ0FBQztBQUUxQyxtQkFBbUI7QUFDbkIsY0FBYywrQkFBK0IsQ0FBQyJ9
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { IStockPrice, IStockQuoteRequest, IStockBatchQuoteRequest } from './stockprice.js';
|
|
2
|
+
export interface IStockProvider {
|
|
3
|
+
name: string;
|
|
4
|
+
priority: number;
|
|
5
|
+
fetchPrice(request: IStockQuoteRequest): Promise<IStockPrice>;
|
|
6
|
+
fetchPrices(request: IStockBatchQuoteRequest): Promise<IStockPrice[]>;
|
|
7
|
+
isAvailable(): Promise<boolean>;
|
|
8
|
+
supportsMarket?(market: string): boolean;
|
|
9
|
+
supportsTicker?(ticker: string): boolean;
|
|
10
|
+
readonly requiresAuth: boolean;
|
|
11
|
+
readonly rateLimit?: {
|
|
12
|
+
requestsPerMinute: number;
|
|
13
|
+
requestsPerDay?: number;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export interface IProviderConfig {
|
|
17
|
+
enabled: boolean;
|
|
18
|
+
priority?: number;
|
|
19
|
+
apiKey?: string;
|
|
20
|
+
timeout?: number;
|
|
21
|
+
retryAttempts?: number;
|
|
22
|
+
retryDelay?: number;
|
|
23
|
+
}
|
|
24
|
+
export interface IProviderRegistry {
|
|
25
|
+
register(provider: IStockProvider, config?: IProviderConfig): void;
|
|
26
|
+
unregister(providerName: string): void;
|
|
27
|
+
getProvider(name: string): IStockProvider | undefined;
|
|
28
|
+
getAllProviders(): IStockProvider[];
|
|
29
|
+
getEnabledProviders(): IStockProvider[];
|
|
30
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface IStockPrice {
|
|
2
|
+
ticker: string;
|
|
3
|
+
price: number;
|
|
4
|
+
currency: string;
|
|
5
|
+
change: number;
|
|
6
|
+
changePercent: number;
|
|
7
|
+
previousClose: number;
|
|
8
|
+
timestamp: Date;
|
|
9
|
+
provider: string;
|
|
10
|
+
marketState: 'PRE' | 'REGULAR' | 'POST' | 'CLOSED';
|
|
11
|
+
exchange?: string;
|
|
12
|
+
exchangeName?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface IStockPriceError {
|
|
15
|
+
ticker: string;
|
|
16
|
+
error: string;
|
|
17
|
+
provider: string;
|
|
18
|
+
timestamp: Date;
|
|
19
|
+
}
|
|
20
|
+
export interface IStockQuoteRequest {
|
|
21
|
+
ticker: string;
|
|
22
|
+
includeExtendedHours?: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface IStockBatchQuoteRequest {
|
|
25
|
+
tickers: string[];
|
|
26
|
+
includeExtendedHours?: boolean;
|
|
27
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { IStockProvider, IProviderConfig } from '../interfaces/provider.js';
|
|
2
|
+
import type { IStockPrice, IStockQuoteRequest, IStockBatchQuoteRequest } from '../interfaces/stockprice.js';
|
|
3
|
+
export declare class YahooFinanceProvider implements IStockProvider {
|
|
4
|
+
private config?;
|
|
5
|
+
name: string;
|
|
6
|
+
priority: number;
|
|
7
|
+
readonly requiresAuth = false;
|
|
8
|
+
readonly rateLimit: {
|
|
9
|
+
requestsPerMinute: number;
|
|
10
|
+
requestsPerDay: any;
|
|
11
|
+
};
|
|
12
|
+
private logger;
|
|
13
|
+
private baseUrl;
|
|
14
|
+
private userAgent;
|
|
15
|
+
constructor(config?: IProviderConfig);
|
|
16
|
+
fetchPrice(request: IStockQuoteRequest): Promise<IStockPrice>;
|
|
17
|
+
fetchPrices(request: IStockBatchQuoteRequest): Promise<IStockPrice[]>;
|
|
18
|
+
isAvailable(): Promise<boolean>;
|
|
19
|
+
supportsMarket(market: string): boolean;
|
|
20
|
+
supportsTicker(ticker: string): boolean;
|
|
21
|
+
private determineMarketState;
|
|
22
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
2
|
+
export class YahooFinanceProvider {
|
|
3
|
+
constructor(config) {
|
|
4
|
+
this.config = config;
|
|
5
|
+
this.name = 'Yahoo Finance';
|
|
6
|
+
this.priority = 100;
|
|
7
|
+
this.requiresAuth = false;
|
|
8
|
+
this.rateLimit = {
|
|
9
|
+
requestsPerMinute: 100, // Conservative estimate
|
|
10
|
+
requestsPerDay: undefined
|
|
11
|
+
};
|
|
12
|
+
this.logger = console;
|
|
13
|
+
this.baseUrl = 'https://query1.finance.yahoo.com';
|
|
14
|
+
this.userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
|
15
|
+
}
|
|
16
|
+
async fetchPrice(request) {
|
|
17
|
+
try {
|
|
18
|
+
const url = `${this.baseUrl}/v8/finance/chart/${request.ticker}`;
|
|
19
|
+
const response = await plugins.smartrequest.getJson(url, {
|
|
20
|
+
headers: {
|
|
21
|
+
'User-Agent': this.userAgent
|
|
22
|
+
},
|
|
23
|
+
timeout: this.config?.timeout || 10000
|
|
24
|
+
});
|
|
25
|
+
const responseData = response.body;
|
|
26
|
+
if (!responseData?.chart?.result?.[0]) {
|
|
27
|
+
throw new Error(`No data found for ticker ${request.ticker}`);
|
|
28
|
+
}
|
|
29
|
+
const data = responseData.chart.result[0];
|
|
30
|
+
const meta = data.meta;
|
|
31
|
+
if (!meta.regularMarketPrice) {
|
|
32
|
+
throw new Error(`No price data available for ${request.ticker}`);
|
|
33
|
+
}
|
|
34
|
+
const stockPrice = {
|
|
35
|
+
ticker: request.ticker.toUpperCase(),
|
|
36
|
+
price: meta.regularMarketPrice,
|
|
37
|
+
currency: meta.currency || 'USD',
|
|
38
|
+
change: meta.regularMarketPrice - meta.previousClose,
|
|
39
|
+
changePercent: ((meta.regularMarketPrice - meta.previousClose) / meta.previousClose) * 100,
|
|
40
|
+
previousClose: meta.previousClose,
|
|
41
|
+
timestamp: new Date(meta.regularMarketTime * 1000),
|
|
42
|
+
provider: this.name,
|
|
43
|
+
marketState: this.determineMarketState(meta),
|
|
44
|
+
exchange: meta.exchange,
|
|
45
|
+
exchangeName: meta.exchangeName
|
|
46
|
+
};
|
|
47
|
+
return stockPrice;
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
console.error(`Failed to fetch price for ${request.ticker}:`, error);
|
|
51
|
+
throw new Error(`Yahoo Finance: Failed to fetch price for ${request.ticker}: ${error.message}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async fetchPrices(request) {
|
|
55
|
+
try {
|
|
56
|
+
const symbols = request.tickers.join(',');
|
|
57
|
+
const url = `${this.baseUrl}/v8/finance/spark?symbols=${symbols}&range=1d&interval=5m`;
|
|
58
|
+
const response = await plugins.smartrequest.getJson(url, {
|
|
59
|
+
headers: {
|
|
60
|
+
'User-Agent': this.userAgent
|
|
61
|
+
},
|
|
62
|
+
timeout: this.config?.timeout || 15000
|
|
63
|
+
});
|
|
64
|
+
const responseData = response.body;
|
|
65
|
+
const prices = [];
|
|
66
|
+
for (const [ticker, data] of Object.entries(responseData)) {
|
|
67
|
+
if (!data || typeof data !== 'object')
|
|
68
|
+
continue;
|
|
69
|
+
const sparkData = data;
|
|
70
|
+
if (!sparkData.previousClose || !sparkData.close?.length) {
|
|
71
|
+
console.warn(`Incomplete data for ${ticker}, skipping`);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const currentPrice = sparkData.close[sparkData.close.length - 1];
|
|
75
|
+
const timestamp = sparkData.timestamp?.[sparkData.timestamp.length - 1];
|
|
76
|
+
prices.push({
|
|
77
|
+
ticker: ticker.toUpperCase(),
|
|
78
|
+
price: currentPrice,
|
|
79
|
+
currency: sparkData.currency || 'USD',
|
|
80
|
+
change: currentPrice - sparkData.previousClose,
|
|
81
|
+
changePercent: ((currentPrice - sparkData.previousClose) / sparkData.previousClose) * 100,
|
|
82
|
+
previousClose: sparkData.previousClose,
|
|
83
|
+
timestamp: timestamp ? new Date(timestamp * 1000) : new Date(),
|
|
84
|
+
provider: this.name,
|
|
85
|
+
marketState: sparkData.marketState || 'REGULAR',
|
|
86
|
+
exchange: sparkData.exchange,
|
|
87
|
+
exchangeName: sparkData.exchangeName
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if (prices.length === 0) {
|
|
91
|
+
throw new Error('No valid price data received from batch request');
|
|
92
|
+
}
|
|
93
|
+
return prices;
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
console.error(`Failed to fetch batch prices:`, error);
|
|
97
|
+
throw new Error(`Yahoo Finance: Failed to fetch batch prices: ${error.message}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async isAvailable() {
|
|
101
|
+
try {
|
|
102
|
+
// Test with a well-known ticker
|
|
103
|
+
await this.fetchPrice({ ticker: 'AAPL' });
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
console.warn('Yahoo Finance provider is not available:', error);
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
supportsMarket(market) {
|
|
112
|
+
// Yahoo Finance supports most major markets
|
|
113
|
+
const supportedMarkets = ['US', 'UK', 'DE', 'FR', 'JP', 'CN', 'HK', 'AU', 'CA'];
|
|
114
|
+
return supportedMarkets.includes(market.toUpperCase());
|
|
115
|
+
}
|
|
116
|
+
supportsTicker(ticker) {
|
|
117
|
+
// Basic validation - Yahoo supports most tickers
|
|
118
|
+
return /^[A-Z0-9\.\-]{1,10}$/.test(ticker.toUpperCase());
|
|
119
|
+
}
|
|
120
|
+
determineMarketState(meta) {
|
|
121
|
+
const marketState = meta.marketState?.toUpperCase();
|
|
122
|
+
switch (marketState) {
|
|
123
|
+
case 'PRE':
|
|
124
|
+
return 'PRE';
|
|
125
|
+
case 'POST':
|
|
126
|
+
return 'POST';
|
|
127
|
+
case 'REGULAR':
|
|
128
|
+
return 'REGULAR';
|
|
129
|
+
default:
|
|
130
|
+
// Check if market is currently open based on timestamps
|
|
131
|
+
const now = Date.now() / 1000;
|
|
132
|
+
const regularMarketTime = meta.regularMarketTime;
|
|
133
|
+
const timeDiff = now - regularMarketTime;
|
|
134
|
+
// If last update was more than 1 hour ago, market is likely closed
|
|
135
|
+
return timeDiff > 3600 ? 'CLOSED' : 'REGULAR';
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJvdmlkZXIueWFob28uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy9zdG9ja3MvcHJvdmlkZXJzL3Byb3ZpZGVyLnlhaG9vLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFJNUMsTUFBTSxPQUFPLG9CQUFvQjtJQWEvQixZQUFvQixNQUF3QjtRQUF4QixXQUFNLEdBQU4sTUFBTSxDQUFrQjtRQVpyQyxTQUFJLEdBQUcsZUFBZSxDQUFDO1FBQ3ZCLGFBQVEsR0FBRyxHQUFHLENBQUM7UUFDTixpQkFBWSxHQUFHLEtBQUssQ0FBQztRQUNyQixjQUFTLEdBQUc7WUFDMUIsaUJBQWlCLEVBQUUsR0FBRyxFQUFFLHdCQUF3QjtZQUNoRCxjQUFjLEVBQUUsU0FBUztTQUMxQixDQUFDO1FBRU0sV0FBTSxHQUFHLE9BQU8sQ0FBQztRQUNqQixZQUFPLEdBQUcsa0NBQWtDLENBQUM7UUFDN0MsY0FBUyxHQUFHLGlIQUFpSCxDQUFDO0lBRXZGLENBQUM7SUFFekMsS0FBSyxDQUFDLFVBQVUsQ0FBQyxPQUEyQjtRQUNqRCxJQUFJLENBQUM7WUFDSCxNQUFNLEdBQUcsR0FBRyxHQUFHLElBQUksQ0FBQyxPQUFPLHFCQUFxQixPQUFPLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDakUsTUFBTSxRQUFRLEdBQUcsTUFBTSxPQUFPLENBQUMsWUFBWSxDQUFDLE9BQU8sQ0FBQyxHQUFHLEVBQUU7Z0JBQ3ZELE9BQU8sRUFBRTtvQkFDUCxZQUFZLEVBQUUsSUFBSSxDQUFDLFNBQVM7aUJBQzdCO2dCQUNELE9BQU8sRUFBRSxJQUFJLENBQUMsTUFBTSxFQUFFLE9BQU8sSUFBSSxLQUFLO2FBQ3ZDLENBQUMsQ0FBQztZQUVILE1BQU0sWUFBWSxHQUFHLFFBQVEsQ0FBQyxJQUFXLENBQUM7WUFFMUMsSUFBSSxDQUFDLFlBQVksRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztnQkFDdEMsTUFBTSxJQUFJLEtBQUssQ0FBQyw0QkFBNEIsT0FBTyxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUM7WUFDaEUsQ0FBQztZQUVELE1BQU0sSUFBSSxHQUFHLFlBQVksQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQzFDLE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUM7WUFFdkIsSUFBSSxDQUFDLElBQUksQ0FBQyxrQkFBa0IsRUFBRSxDQUFDO2dCQUM3QixNQUFNLElBQUksS0FBSyxDQUFDLCtCQUErQixPQUFPLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQztZQUNuRSxDQUFDO1lBRUQsTUFBTSxVQUFVLEdBQWdCO2dCQUM5QixNQUFNLEVBQUUsT0FBTyxDQUFDLE1BQU0sQ0FBQyxXQUFXLEVBQUU7Z0JBQ3BDLEtBQUssRUFBRSxJQUFJLENBQUMsa0JBQWtCO2dCQUM5QixRQUFRLEVBQUUsSUFBSSxDQUFDLFFBQVEsSUFBSSxLQUFLO2dCQUNoQyxNQUFNLEVBQUUsSUFBSSxDQUFDLGtCQUFrQixHQUFHLElBQUksQ0FBQyxhQUFhO2dCQUNwRCxhQUFhLEVBQUUsQ0FBQyxDQUFDLElBQUksQ0FBQyxrQkFBa0IsR0FBRyxJQUFJLENBQUMsYUFBYSxDQUFDLEdBQUcsSUFBSSxDQUFDLGFBQWEsQ0FBQyxHQUFHLEdBQUc7Z0JBQzFGLGFBQWEsRUFBRSxJQUFJLENBQUMsYUFBYTtnQkFDakMsU0FBUyxFQUFFLElBQUksSUFBSSxDQUFDLElBQUksQ0FBQyxpQkFBaUIsR0FBRyxJQUFJLENBQUM7Z0JBQ2xELFFBQVEsRUFBRSxJQUFJLENBQUMsSUFBSTtnQkFDbkIsV0FBVyxFQUFFLElBQUksQ0FBQyxvQkFBb0IsQ0FBQyxJQUFJLENBQUM7Z0JBQzVDLFFBQVEsRUFBRSxJQUFJLENBQUMsUUFBUTtnQkFDdkIsWUFBWSxFQUFFLElBQUksQ0FBQyxZQUFZO2FBQ2hDLENBQUM7WUFFRixPQUFPLFVBQVUsQ0FBQztRQUNwQixDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLE9BQU8sQ0FBQyxLQUFLLENBQUMsNkJBQTZCLE9BQU8sQ0FBQyxNQUFNLEdBQUcsRUFBRSxLQUFLLENBQUMsQ0FBQztZQUNyRSxNQUFNLElBQUksS0FBSyxDQUFDLDRDQUE0QyxPQUFPLENBQUMsTUFBTSxLQUFLLEtBQUssQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFDO1FBQ2xHLENBQUM7SUFDSCxDQUFDO0lBRU0sS0FBSyxDQUFDLFdBQVcsQ0FBQyxPQUFnQztRQUN2RCxJQUFJLENBQUM7WUFDSCxNQUFNLE9BQU8sR0FBRyxPQUFPLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQztZQUMxQyxNQUFNLEdBQUcsR0FBRyxHQUFHLElBQUksQ0FBQyxPQUFPLDZCQUE2QixPQUFPLHVCQUF1QixDQUFDO1lBRXZGLE1BQU0sUUFBUSxHQUFHLE1BQU0sT0FBTyxDQUFDLFlBQVksQ0FBQyxPQUFPLENBQUMsR0FBRyxFQUFFO2dCQUN2RCxPQUFPLEVBQUU7b0JBQ1AsWUFBWSxFQUFFLElBQUksQ0FBQyxTQUFTO2lCQUM3QjtnQkFDRCxPQUFPLEVBQUUsSUFBSSxDQUFDLE1BQU0sRUFBRSxPQUFPLElBQUksS0FBSzthQUN2QyxDQUFDLENBQUM7WUFFSCxNQUFNLFlBQVksR0FBRyxRQUFRLENBQUMsSUFBVyxDQUFDO1lBQzFDLE1BQU0sTUFBTSxHQUFrQixFQUFFLENBQUM7WUFFakMsS0FBSyxNQUFNLENBQUMsTUFBTSxFQUFFLElBQUksQ0FBQyxJQUFJLE1BQU0sQ0FBQyxPQUFPLENBQUMsWUFBWSxDQUFDLEVBQUUsQ0FBQztnQkFDMUQsSUFBSSxDQUFDLElBQUksSUFBSSxPQUFPLElBQUksS0FBSyxRQUFRO29CQUFFLFNBQVM7Z0JBRWhELE1BQU0sU0FBUyxHQUFHLElBQVcsQ0FBQztnQkFDOUIsSUFBSSxDQUFDLFNBQVMsQ0FBQyxhQUFhLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxFQUFFLE1BQU0sRUFBRSxDQUFDO29CQUN6RCxPQUFPLENBQUMsSUFBSSxDQUFDLHVCQUF1QixNQUFNLFlBQVksQ0FBQyxDQUFDO29CQUN4RCxTQUFTO2dCQUNYLENBQUM7Z0JBRUQsTUFBTSxZQUFZLEdBQUcsU0FBUyxDQUFDLEtBQUssQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsQ0FBQztnQkFDakUsTUFBTSxTQUFTLEdBQUcsU0FBUyxDQUFDLFNBQVMsRUFBRSxDQUFDLFNBQVMsQ0FBQyxTQUFTLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxDQUFDO2dCQUV4RSxNQUFNLENBQUMsSUFBSSxDQUFDO29CQUNWLE1BQU0sRUFBRSxNQUFNLENBQUMsV0FBVyxFQUFFO29CQUM1QixLQUFLLEVBQUUsWUFBWTtvQkFDbkIsUUFBUSxFQUFFLFNBQVMsQ0FBQyxRQUFRLElBQUksS0FBSztvQkFDckMsTUFBTSxFQUFFLFlBQVksR0FBRyxTQUFTLENBQUMsYUFBYTtvQkFDOUMsYUFBYSxFQUFFLENBQUMsQ0FBQyxZQUFZLEdBQUcsU0FBUyxDQUFDLGFBQWEsQ0FBQyxHQUFHLFNBQVMsQ0FBQyxhQUFhLENBQUMsR0FBRyxHQUFHO29CQUN6RixhQUFhLEVBQUUsU0FBUyxDQUFDLGFBQWE7b0JBQ3RDLFNBQVMsRUFBRSxTQUFTLENBQUMsQ0FBQyxDQUFDLElBQUksSUFBSSxDQUFDLFNBQVMsR0FBRyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxJQUFJLEVBQUU7b0JBQzlELFFBQVEsRUFBRSxJQUFJLENBQUMsSUFBSTtvQkFDbkIsV0FBVyxFQUFFLFNBQVMsQ0FBQyxXQUFXLElBQUksU0FBUztvQkFDL0MsUUFBUSxFQUFFLFNBQVMsQ0FBQyxRQUFRO29CQUM1QixZQUFZLEVBQUUsU0FBUyxDQUFDLFlBQVk7aUJBQ3JDLENBQUMsQ0FBQztZQUNMLENBQUM7WUFFRCxJQUFJLE1BQU0sQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFLENBQUM7Z0JBQ3hCLE1BQU0sSUFBSSxLQUFLLENBQUMsaURBQWlELENBQUMsQ0FBQztZQUNyRSxDQUFDO1lBRUQsT0FBTyxNQUFNLENBQUM7UUFDaEIsQ0FBQztRQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7WUFDZixPQUFPLENBQUMsS0FBSyxDQUFDLCtCQUErQixFQUFFLEtBQUssQ0FBQyxDQUFDO1lBQ3RELE1BQU0sSUFBSSxLQUFLLENBQUMsZ0RBQWdELEtBQUssQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFDO1FBQ25GLENBQUM7SUFDSCxDQUFDO0lBRU0sS0FBSyxDQUFDLFdBQVc7UUFDdEIsSUFBSSxDQUFDO1lBQ0gsZ0NBQWdDO1lBQ2hDLE1BQU0sSUFBSSxDQUFDLFVBQVUsQ0FBQyxFQUFFLE1BQU0sRUFBRSxNQUFNLEVBQUUsQ0FBQyxDQUFDO1lBQzFDLE9BQU8sSUFBSSxDQUFDO1FBQ2QsQ0FBQztRQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7WUFDZixPQUFPLENBQUMsSUFBSSxDQUFDLDBDQUEwQyxFQUFFLEtBQUssQ0FBQyxDQUFDO1lBQ2hFLE9BQU8sS0FBSyxDQUFDO1FBQ2YsQ0FBQztJQUNILENBQUM7SUFFTSxjQUFjLENBQUMsTUFBYztRQUNsQyw0Q0FBNEM7UUFDNUMsTUFBTSxnQkFBZ0IsR0FBRyxDQUFDLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxDQUFDLENBQUM7UUFDaEYsT0FBTyxnQkFBZ0IsQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLFdBQVcsRUFBRSxDQUFDLENBQUM7SUFDekQsQ0FBQztJQUVNLGNBQWMsQ0FBQyxNQUFjO1FBQ2xDLGlEQUFpRDtRQUNqRCxPQUFPLHNCQUFzQixDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsV0FBVyxFQUFFLENBQUMsQ0FBQztJQUMzRCxDQUFDO0lBRU8sb0JBQW9CLENBQUMsSUFBUztRQUNwQyxNQUFNLFdBQVcsR0FBRyxJQUFJLENBQUMsV0FBVyxFQUFFLFdBQVcsRUFBRSxDQUFDO1FBRXBELFFBQVEsV0FBVyxFQUFFLENBQUM7WUFDcEIsS0FBSyxLQUFLO2dCQUNSLE9BQU8sS0FBSyxDQUFDO1lBQ2YsS0FBSyxNQUFNO2dCQUNULE9BQU8sTUFBTSxDQUFDO1lBQ2hCLEtBQUssU0FBUztnQkFDWixPQUFPLFNBQVMsQ0FBQztZQUNuQjtnQkFDRSx3REFBd0Q7Z0JBQ3hELE1BQU0sR0FBRyxHQUFHLElBQUksQ0FBQyxHQUFHLEVBQUUsR0FBRyxJQUFJLENBQUM7Z0JBQzlCLE1BQU0saUJBQWlCLEdBQUcsSUFBSSxDQUFDLGlCQUFpQixDQUFDO2dCQUNqRCxNQUFNLFFBQVEsR0FBRyxHQUFHLEdBQUcsaUJBQWlCLENBQUM7Z0JBRXpDLG1FQUFtRTtnQkFDbkUsT0FBTyxRQUFRLEdBQUcsSUFBSSxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQztRQUNsRCxDQUFDO0lBQ0gsQ0FBQztDQUNGIn0=
|