@rtbnext/core 2.0.0-alpha.1

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.
Files changed (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +11 -0
  3. package/dist/abstract/Cache.d.ts +9 -0
  4. package/dist/abstract/Cache.js +19 -0
  5. package/dist/abstract/Index.d.ts +22 -0
  6. package/dist/abstract/Index.js +81 -0
  7. package/dist/abstract/Job.d.ts +19 -0
  8. package/dist/abstract/Job.js +49 -0
  9. package/dist/abstract/Snapshot.d.ts +22 -0
  10. package/dist/abstract/Snapshot.js +78 -0
  11. package/dist/bin/cli.d.ts +2 -0
  12. package/dist/bin/cli.js +25 -0
  13. package/dist/bin/cron.d.ts +2 -0
  14. package/dist/bin/cron.js +4 -0
  15. package/dist/core/Config.d.ts +30 -0
  16. package/dist/core/Config.js +66 -0
  17. package/dist/core/Cron.d.ts +12 -0
  18. package/dist/core/Cron.js +52 -0
  19. package/dist/core/Fetch.d.ts +28 -0
  20. package/dist/core/Fetch.js +172 -0
  21. package/dist/core/Logger.d.ts +30 -0
  22. package/dist/core/Logger.js +92 -0
  23. package/dist/core/Queue.d.ts +37 -0
  24. package/dist/core/Queue.js +136 -0
  25. package/dist/core/Storage.d.ts +28 -0
  26. package/dist/core/Storage.js +166 -0
  27. package/dist/core/Utils.d.ts +33 -0
  28. package/dist/core/Utils.js +167 -0
  29. package/dist/interfaces/cache.d.ts +6 -0
  30. package/dist/interfaces/config.d.ts +21 -0
  31. package/dist/interfaces/cron.d.ts +3 -0
  32. package/dist/interfaces/fetch.d.ts +13 -0
  33. package/dist/interfaces/filter.d.ts +12 -0
  34. package/dist/interfaces/index.d.ts +30 -0
  35. package/dist/interfaces/job.d.ts +9 -0
  36. package/dist/interfaces/list.d.ts +9 -0
  37. package/dist/interfaces/logger.d.ts +20 -0
  38. package/dist/interfaces/mover.d.ts +7 -0
  39. package/dist/interfaces/parser.d.ts +68 -0
  40. package/dist/interfaces/profile.d.ts +30 -0
  41. package/dist/interfaces/queue.d.ts +17 -0
  42. package/dist/interfaces/snapshot.d.ts +16 -0
  43. package/dist/interfaces/stats.d.ts +45 -0
  44. package/dist/interfaces/storage.d.ts +16 -0
  45. package/dist/job/Alias.d.ts +8 -0
  46. package/dist/job/Alias.js +42 -0
  47. package/dist/job/Annual.d.ts +8 -0
  48. package/dist/job/Annual.js +41 -0
  49. package/dist/job/List.d.ts +11 -0
  50. package/dist/job/List.js +101 -0
  51. package/dist/job/Merge.d.ts +10 -0
  52. package/dist/job/Merge.js +59 -0
  53. package/dist/job/Move.d.ts +7 -0
  54. package/dist/job/Move.js +33 -0
  55. package/dist/job/Performance.d.ts +8 -0
  56. package/dist/job/Performance.js +27 -0
  57. package/dist/job/Profile.d.ts +11 -0
  58. package/dist/job/Profile.js +76 -0
  59. package/dist/job/Queue.d.ts +8 -0
  60. package/dist/job/Queue.js +54 -0
  61. package/dist/job/RTB.d.ts +12 -0
  62. package/dist/job/RTB.js +121 -0
  63. package/dist/job/Stats.d.ts +11 -0
  64. package/dist/job/Stats.js +46 -0
  65. package/dist/job/Top10.d.ts +9 -0
  66. package/dist/job/Top10.js +48 -0
  67. package/dist/job/Wiki.d.ts +9 -0
  68. package/dist/job/Wiki.js +40 -0
  69. package/dist/job/index.d.ts +26 -0
  70. package/dist/job/index.js +26 -0
  71. package/dist/lib/const.d.ts +31 -0
  72. package/dist/lib/const.js +74 -0
  73. package/dist/lib/list.d.ts +90 -0
  74. package/dist/lib/list.js +72 -0
  75. package/dist/lib/regex.d.ts +7 -0
  76. package/dist/lib/regex.js +7 -0
  77. package/dist/model/Filter.d.ts +28 -0
  78. package/dist/model/Filter.js +122 -0
  79. package/dist/model/List.d.ts +12 -0
  80. package/dist/model/List.js +43 -0
  81. package/dist/model/ListIndex.d.ts +8 -0
  82. package/dist/model/ListIndex.js +10 -0
  83. package/dist/model/Mover.d.ts +15 -0
  84. package/dist/model/Mover.js +74 -0
  85. package/dist/model/Profile.d.ts +49 -0
  86. package/dist/model/Profile.js +181 -0
  87. package/dist/model/ProfileIndex.d.ts +20 -0
  88. package/dist/model/ProfileIndex.js +140 -0
  89. package/dist/model/Stats.d.ts +56 -0
  90. package/dist/model/Stats.js +435 -0
  91. package/dist/parser/BillionairesListParser.d.ts +3 -0
  92. package/dist/parser/BillionairesListParser.js +2 -0
  93. package/dist/parser/ListParser.d.ts +7 -0
  94. package/dist/parser/ListParser.js +11 -0
  95. package/dist/parser/Parser.d.ts +43 -0
  96. package/dist/parser/Parser.js +146 -0
  97. package/dist/parser/PersonListParser.d.ts +29 -0
  98. package/dist/parser/PersonListParser.js +111 -0
  99. package/dist/parser/ProfileParser.d.ts +44 -0
  100. package/dist/parser/ProfileParser.js +193 -0
  101. package/dist/parser/RTBListParser.d.ts +15 -0
  102. package/dist/parser/RTBListParser.js +91 -0
  103. package/dist/types/annual.d.ts +7 -0
  104. package/dist/types/config.d.ts +35 -0
  105. package/dist/types/fetch.d.ts +3 -0
  106. package/dist/types/generic.d.ts +10 -0
  107. package/dist/types/job.d.ts +71 -0
  108. package/dist/types/list.d.ts +49 -0
  109. package/dist/types/parser.d.ts +7 -0
  110. package/dist/types/profile.d.ts +9 -0
  111. package/dist/types/queue.d.ts +15 -0
  112. package/dist/types/response.d.ts +183 -0
  113. package/dist/types/storage.d.ts +3 -0
  114. package/dist/types/wiki.d.ts +1 -0
  115. package/dist/utils/Annual.d.ts +7 -0
  116. package/dist/utils/Annual.js +99 -0
  117. package/dist/utils/Performance.d.ts +8 -0
  118. package/dist/utils/Performance.js +39 -0
  119. package/dist/utils/ProfileManager.d.ts +24 -0
  120. package/dist/utils/ProfileManager.js +60 -0
  121. package/dist/utils/ProfileMerger.d.ts +11 -0
  122. package/dist/utils/ProfileMerger.js +67 -0
  123. package/dist/utils/Ranking.d.ts +11 -0
  124. package/dist/utils/Ranking.js +77 -0
  125. package/dist/utils/Wiki.d.ts +11 -0
  126. package/dist/utils/Wiki.js +168 -0
  127. package/package.json +45 -0
@@ -0,0 +1,40 @@
1
+ import { Job } from '../abstract/Job.js';
2
+ import { Profile } from '../model/Profile.js';
3
+ import { Wiki } from '../utils/Wiki.js';
4
+ export class WikiJob extends Job {
5
+ constructor(options) {
6
+ super(options, 'Wiki');
7
+ }
8
+ // --- job runner ---
9
+ async update(profile) {
10
+ this.log(`Updating wiki for profile: ${profile.getUri()}`);
11
+ const wiki = await Wiki.fromProfileData(profile.getData());
12
+ if (!wiki) throw new Error(`No wiki data found for profile: ${profile.getUri()}`);
13
+ profile.updateData({ wiki });
14
+ profile.save();
15
+ }
16
+ async assign(profile, title) {
17
+ this.log(`Assigning wiki page "${title}" to profile: ${profile.getUri()}`);
18
+ const wiki = await Wiki.queryWikiPage(title);
19
+ if (!wiki) throw new Error(`Wiki page not found: ${title}`);
20
+ profile.updateData({ wiki });
21
+ profile.save();
22
+ }
23
+ async run() {
24
+ await this.protect(async () => {
25
+ const profile = Profile.find(this.options.profile);
26
+ if (!profile) throw new Error(`Profile not found: ${this.options.profile}`);
27
+ if (this.options.assign) await this.assign(profile, this.options.assign);
28
+ else await this.update(profile);
29
+ });
30
+ }
31
+ // --- command definition ---
32
+ static command = {
33
+ id: 'wiki',
34
+ desc: 'Update and assign wiki data to a profile',
35
+ options: [
36
+ { name: '-p, --profile <URI>', desc: 'The profile URI to process', required: true },
37
+ { name: '--assign <TITLE>', desc: 'Assign wiki data from the specified wiki title to the profile' }
38
+ ]
39
+ };
40
+ }
@@ -0,0 +1,26 @@
1
+ import { AliasJob } from './Alias.js';
2
+ import { AnnualJob } from './Annual.js';
3
+ import { ListJob } from './List.js';
4
+ import { MergeJob } from './Merge.js';
5
+ import { MoveJob } from './Move.js';
6
+ import { PerformanceJob } from './Performance.js';
7
+ import { ProfileJob } from './Profile.js';
8
+ import { QueueJob } from './Queue.js';
9
+ import { RTBJob } from './RTB.js';
10
+ import { StatsJob } from './Stats.js';
11
+ import { Top10Job } from './Top10.js';
12
+ import { WikiJob } from './Wiki.js';
13
+ export declare const JOBS: readonly [
14
+ typeof AliasJob,
15
+ typeof AnnualJob,
16
+ typeof ListJob,
17
+ typeof MergeJob,
18
+ typeof MoveJob,
19
+ typeof PerformanceJob,
20
+ typeof ProfileJob,
21
+ typeof QueueJob,
22
+ typeof RTBJob,
23
+ typeof StatsJob,
24
+ typeof Top10Job,
25
+ typeof WikiJob
26
+ ];
@@ -0,0 +1,26 @@
1
+ import { AliasJob } from './Alias.js';
2
+ import { AnnualJob } from './Annual.js';
3
+ import { ListJob } from './List.js';
4
+ import { MergeJob } from './Merge.js';
5
+ import { MoveJob } from './Move.js';
6
+ import { PerformanceJob } from './Performance.js';
7
+ import { ProfileJob } from './Profile.js';
8
+ import { QueueJob } from './Queue.js';
9
+ import { RTBJob } from './RTB.js';
10
+ import { StatsJob } from './Stats.js';
11
+ import { Top10Job } from './Top10.js';
12
+ import { WikiJob } from './Wiki.js';
13
+ export const JOBS = [
14
+ AliasJob,
15
+ AnnualJob,
16
+ ListJob,
17
+ MergeJob,
18
+ MoveJob,
19
+ PerformanceJob,
20
+ ProfileJob,
21
+ QueueJob,
22
+ RTBJob,
23
+ StatsJob,
24
+ Top10Job,
25
+ WikiJob
26
+ ];
@@ -0,0 +1,31 @@
1
+ import type {
2
+ TAssetType,
3
+ TChangeFlag,
4
+ TChildrenGroup,
5
+ TFilterGroup,
6
+ TFilterSpecial,
7
+ TGender,
8
+ TIndustry,
9
+ TMaritalStatus,
10
+ TPercentiles,
11
+ TRelationType,
12
+ TSelfMadeRank,
13
+ TStatsGroup,
14
+ TWealthSpread
15
+ } from '@rtbnext/schema/src/base/const';
16
+ import type { TIndustryResolver, TMaritalStatusResolver } from '../types/generic';
17
+ export declare const Industry: TIndustry[];
18
+ export declare const Gender: TGender[];
19
+ export declare const MaritalStatus: TMaritalStatus[];
20
+ export declare const SelfMadeRank: TSelfMadeRank[];
21
+ export declare const RelationType: TRelationType[];
22
+ export declare const AssetType: TAssetType[];
23
+ export declare const ChangeFlag: TChangeFlag[];
24
+ export declare const StatsGroup: TStatsGroup[];
25
+ export declare const ChildrenGroup: TChildrenGroup[];
26
+ export declare const Percentiles: TPercentiles[];
27
+ export declare const WealthSpread: TWealthSpread[];
28
+ export declare const FilterGroup: TFilterGroup[];
29
+ export declare const FilterSpecial: TFilterSpecial[];
30
+ export declare const MaritalStatusResolver: TMaritalStatusResolver;
31
+ export declare const IndustryResolver: TIndustryResolver;
@@ -0,0 +1,74 @@
1
+ // --- basics ---
2
+ export const Industry = [
3
+ 'automotive',
4
+ 'diversified',
5
+ 'energy',
6
+ 'engineering',
7
+ 'finance',
8
+ 'foodstuff',
9
+ 'gambling',
10
+ 'healthcare',
11
+ 'logistics',
12
+ 'manufacturing',
13
+ 'media',
14
+ 'mining',
15
+ 'property',
16
+ 'retail',
17
+ 'service',
18
+ 'sports',
19
+ 'technology',
20
+ 'telecom'
21
+ ];
22
+ export const Gender = ['m', 'f', 'd'];
23
+ export const MaritalStatus = [
24
+ 'single',
25
+ 'relationship',
26
+ 'married',
27
+ 'remarried',
28
+ 'engaged',
29
+ 'separated',
30
+ 'divorced',
31
+ 'widowed'
32
+ ];
33
+ export const SelfMadeRank = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'];
34
+ export const RelationType = ['person', 'organization', 'place', 'unknown'];
35
+ export const AssetType = ['public', 'private', 'misc'];
36
+ export const ChangeFlag = ['up', 'down', 'unchanged', 'new', 'returned', 'dropoff', 'unknown'];
37
+ // --- stats ---
38
+ export const StatsGroup = ['industry', 'citizenship'];
39
+ export const ChildrenGroup = ['none', 'one', 'two', 'three', 'four', '5-to-10', 'over-10'];
40
+ export const Percentiles = ['10th', '25th', '50th', '75th', '90th', '95th', '99th'];
41
+ export const WealthSpread = ['1', '2', '5', '10', '20', '50', '100', '200', '500', '1000'];
42
+ // --- filter ---
43
+ export const FilterGroup = ['industry', 'citizenship', 'country', 'state', 'gender', 'age', 'maritalStatus', 'special'];
44
+ export const FilterSpecial = ['deceased', 'dropOff', 'family', 'selfMade'];
45
+ // --- resolver ---
46
+ export const MaritalStatusResolver = {
47
+ 'single': 'single',
48
+ 'in-relationship': 'relationship',
49
+ 'married': 'married',
50
+ 'remarried': 'remarried',
51
+ 'engaged': 'engaged',
52
+ 'separated': 'separated',
53
+ 'divorced': 'divorced',
54
+ 'widowed': 'widowed'
55
+ };
56
+ export const IndustryResolver = {
57
+ 'technology': 'technology',
58
+ 'fashion-retail': 'retail',
59
+ 'finance-investments': 'finance',
60
+ 'diversified': 'diversified',
61
+ 'telecom': 'telecom',
62
+ 'energy': 'energy',
63
+ 'metals-mining': 'mining',
64
+ 'gambling-casinos': 'gambling',
65
+ 'healthcare': 'healthcare',
66
+ 'manufacturing': 'manufacturing',
67
+ 'logistics': 'logistics',
68
+ 'automotive': 'automotive',
69
+ 'media-entertainment': 'media',
70
+ 'construction-engineering': 'engineering',
71
+ 'sports': 'sports',
72
+ 'real-estate': 'property',
73
+ 'service': 'service'
74
+ };
@@ -0,0 +1,90 @@
1
+ import { BillionairesListParser } from '../parser/BillionairesListParser.js';
2
+ import { PersonListParser } from '../parser/PersonListParser.js';
3
+ import { RTBListParser } from '../parser/RTBListParser.js';
4
+ import type { TListIndexItemCtx, TListTypes, TPersonListItemCtx, TRTBListItemCtx } from '../types/list';
5
+ export declare const LISTS: {
6
+ readonly rtb: {
7
+ readonly lists: readonly ['rtb'];
8
+ readonly parser: typeof RTBListParser;
9
+ readonly indexItem: () => {
10
+ uri: string;
11
+ name: string;
12
+ shortName: string;
13
+ desc: string;
14
+ text: string;
15
+ columns: string[];
16
+ filters: string[];
17
+ };
18
+ readonly listItem: (ctx: TRTBListItemCtx) => {
19
+ flag: import('@rtbnext/schema/src/base/const').TChangeFlag;
20
+ rankDiff: number | undefined;
21
+ today: import('@rtbnext/schema/src/base/assets').TChangeItem | undefined;
22
+ ytd: import('@rtbnext/schema/src/base/assets').TChangeItem | undefined;
23
+ uri: string;
24
+ rank: number;
25
+ networth: number;
26
+ name: string;
27
+ gender: import('@rtbnext/schema/src/base/const').TGender | undefined;
28
+ age: number | undefined;
29
+ citizenship: string | undefined;
30
+ industry: import('@rtbnext/schema/src/base/const').TIndustry;
31
+ source: string[];
32
+ };
33
+ };
34
+ readonly billionaires: {
35
+ readonly lists: readonly ['billionaires', 'forbes-400'];
36
+ readonly parser: typeof BillionairesListParser;
37
+ readonly indexItem: (
38
+ uri: string,
39
+ ctx: TListIndexItemCtx
40
+ ) => {
41
+ uri: string;
42
+ name: string;
43
+ shortName: string;
44
+ desc: string;
45
+ text: string;
46
+ columns: string[];
47
+ filters: string[];
48
+ };
49
+ readonly listItem: (ctx: TPersonListItemCtx) => {
50
+ selfMadeRank: import('@rtbnext/schema/src/base/const').TSelfMadeRank | undefined;
51
+ philanthropyScore: number | undefined;
52
+ uri: string;
53
+ rank: number;
54
+ networth: number;
55
+ name: string;
56
+ gender: import('@rtbnext/schema/src/base/const').TGender | undefined;
57
+ age: number | undefined;
58
+ citizenship: string | undefined;
59
+ industry: import('@rtbnext/schema/src/base/const').TIndustry;
60
+ source: string[];
61
+ };
62
+ };
63
+ readonly person: {
64
+ readonly parser: typeof PersonListParser;
65
+ readonly indexItem: (
66
+ uri: string,
67
+ ctx: TListIndexItemCtx
68
+ ) => {
69
+ uri: string;
70
+ name: string;
71
+ shortName: string;
72
+ desc: string;
73
+ text: string;
74
+ columns: string[];
75
+ filters: string[];
76
+ };
77
+ readonly listItem: (ctx: TPersonListItemCtx) => {
78
+ uri: string;
79
+ rank: number;
80
+ networth: number;
81
+ name: string;
82
+ gender: import('@rtbnext/schema/src/base/const').TGender | undefined;
83
+ age: number | undefined;
84
+ citizenship: string | undefined;
85
+ industry: import('@rtbnext/schema/src/base/const').TIndustry;
86
+ source: string[];
87
+ };
88
+ };
89
+ };
90
+ export declare const getListConfigByUri: (uri: string) => (typeof LISTS)[TListTypes];
@@ -0,0 +1,72 @@
1
+ import { Utils } from '../core/Utils.js';
2
+ import { BillionairesListParser } from '../parser/BillionairesListParser.js';
3
+ import { PersonListParser } from '../parser/PersonListParser.js';
4
+ import { RTBListParser } from '../parser/RTBListParser.js';
5
+ const personListItem = ctx => ({
6
+ uri: ctx.parsed.uri(),
7
+ rank: ctx.parsed.rank(),
8
+ networth: ctx.parsed.networth(),
9
+ name: ctx.profileData.info.name.shortName,
10
+ gender: ctx.profileData.info?.gender,
11
+ age: ctx.parsed.age(),
12
+ citizenship: ctx.profileData.info?.citizenship,
13
+ industry: ctx.profileData.info?.industry,
14
+ source: ctx.profileData.info?.source
15
+ });
16
+ export const LISTS = {
17
+ rtb: {
18
+ lists: ['rtb'],
19
+ parser: RTBListParser,
20
+ indexItem: () => ({
21
+ uri: 'rtb',
22
+ name: 'The World’s Real-Time Billionaires',
23
+ shortName: 'Real-Time Billionaires',
24
+ desc: 'Today’s richest people in the world',
25
+ text: 'todays richest people world',
26
+ columns: ['rank', 'diff', 'profile', 'networth', 'today', 'ytd', 'age', 'citizenship', 'source'],
27
+ filters: ['gender', 'industry', 'citizenship', 'diff', 'age']
28
+ }),
29
+ listItem: ctx => ({
30
+ ...personListItem(ctx),
31
+ flag: ctx.flag,
32
+ rankDiff: ctx.rankDiff,
33
+ today: ctx.realtime?.today,
34
+ ytd: ctx.realtime?.ytd
35
+ })
36
+ },
37
+ billionaires: {
38
+ lists: ['billionaires', 'forbes-400'],
39
+ parser: BillionairesListParser,
40
+ indexItem: (uri, ctx) => ({
41
+ uri,
42
+ name: ctx.name,
43
+ shortName: ctx.name,
44
+ desc: ctx.desc,
45
+ text: Utils.buildSearchText(ctx.desc),
46
+ columns: ['rank', 'profile', 'networth', 'age', 'citizenship', 'selfMadeRank', 'philanthropyScore', 'source'],
47
+ filters: ['gender', 'industry', 'citizenship', 'age', 'selfMadeRank', 'philanthropyScore']
48
+ }),
49
+ listItem: ctx => ({
50
+ ...personListItem(ctx),
51
+ selfMadeRank: ctx.parsed.selfMade()?.rank,
52
+ philanthropyScore: ctx.parsed.philanthropyScore()
53
+ })
54
+ },
55
+ person: {
56
+ parser: PersonListParser,
57
+ indexItem: (uri, ctx) => ({
58
+ uri,
59
+ name: ctx.name,
60
+ shortName: ctx.name,
61
+ desc: ctx.desc,
62
+ text: Utils.buildSearchText(ctx.desc),
63
+ columns: ['rank', 'profile', 'networth', 'age', 'citizenship', 'source'],
64
+ filters: ['gender', 'industry', 'citizenship', 'age']
65
+ }),
66
+ listItem: personListItem
67
+ }
68
+ };
69
+ export const getListConfigByUri = uri => {
70
+ for (const config of Object.values(LISTS)) if ('lists' in config && config.lists.includes(uri)) return config;
71
+ return LISTS.person;
72
+ };
@@ -0,0 +1,7 @@
1
+ export declare const REGEX_DIACRITICS: RegExp;
2
+ export declare const REGEX_FAMILY: RegExp;
3
+ export declare const REGEX_NOALNUM: RegExp;
4
+ export declare const REGEX_NONUM: RegExp;
5
+ export declare const REGEX_SPACE_DELIMITER: RegExp;
6
+ export declare const REGEX_SPACES: RegExp;
7
+ export declare const REGEX_URI_CLEANUP: RegExp;
@@ -0,0 +1,7 @@
1
+ export const REGEX_DIACRITICS = /[\u0300-\u036f]/g;
2
+ export const REGEX_FAMILY = /&\s*family/i;
3
+ export const REGEX_NOALNUM = /[^a-z0-9]+/g;
4
+ export const REGEX_NONUM = /[^\d]+/g;
5
+ export const REGEX_SPACE_DELIMITER = /\s+/;
6
+ export const REGEX_SPACES = /\s+/g;
7
+ export const REGEX_URI_CLEANUP = /-(family|\d+)$/i;
@@ -0,0 +1,28 @@
1
+ import type { TFilterGroup, TFilterSpecial } from '@rtbnext/schema/src/base/const';
2
+ import type { TFilter, TFilterList } from '@rtbnext/schema/src/model/filter';
3
+ import type { TProfileData } from '@rtbnext/schema/src/model/profile';
4
+ import type { IFilter } from '../interfaces/filter';
5
+ export declare class Filter implements IFilter {
6
+ private static readonly storage;
7
+ private static instance;
8
+ private data;
9
+ private constructor();
10
+ private initDB;
11
+ private prepFilter;
12
+ private setFilterData;
13
+ private splitPath;
14
+ private joinPath;
15
+ resolvePath(path: string): string | false;
16
+ private loadFilter;
17
+ private saveFilter;
18
+ private saveGroup;
19
+ private saveSpecial;
20
+ getFilter(group: TFilterGroup, key: string): TFilter | undefined;
21
+ getFilterByPath(path: string): TFilter | undefined;
22
+ getGroup(group: TFilterGroup): Record<string, TFilter> | undefined;
23
+ getSpecial(special: TFilterSpecial): TFilter | undefined;
24
+ has(path: string, uriLike: string): boolean;
25
+ save(col: Partial<TFilterList>): void;
26
+ static getInstance(): IFilter;
27
+ static aggregate(data: TProfileData, filter: Partial<TFilterList>): void;
28
+ }
@@ -0,0 +1,122 @@
1
+ import { join } from 'node:path';
2
+ import { log } from '../core/Logger.js';
3
+ import { Storage } from '../core/Storage.js';
4
+ import { Utils } from '../core/Utils.js';
5
+ import { FilterGroup, FilterSpecial } from '../lib/const.js';
6
+ import { Parser } from '../parser/Parser.js';
7
+ export class Filter {
8
+ static storage = Storage.getInstance();
9
+ static instance;
10
+ data = {};
11
+ constructor() {
12
+ this.initDB();
13
+ }
14
+ initDB() {
15
+ log.debug('Initializing filter storage paths');
16
+ FilterGroup.forEach(group => Filter.storage.ensurePath(join('filter', group), true));
17
+ }
18
+ // --- helper ---
19
+ prepFilter(list) {
20
+ return [...new Map(list.map(i => [i.uri, i])).values()].sort((a, b) => a.uri.localeCompare(b.uri));
21
+ }
22
+ setFilterData(group, key, filter) {
23
+ (this.data[group] ??= {})[key] = filter;
24
+ }
25
+ // --- path helper ---
26
+ splitPath(path) {
27
+ const [group, key] = path.replace('.json', '').split('/').slice(-2);
28
+ return FilterGroup.includes(group) && key ? [group, key] : undefined;
29
+ }
30
+ joinPath(group, key) {
31
+ return group && key ? join('filter', group, `${key}.json`) : false;
32
+ }
33
+ resolvePath(path) {
34
+ return this.joinPath(...(this.splitPath(path) ?? []));
35
+ }
36
+ // --- load & save filter ---
37
+ loadFilter(group, key) {
38
+ log.debug(`Loading filter from ${group}/${key}`);
39
+ return log.catch(() => {
40
+ const resolved = this.joinPath(group, key);
41
+ if (!resolved) throw new Error(`Invalid filter path: ${group}/${key}`);
42
+ const filter = Filter.storage.readJSON(resolved);
43
+ if (!filter) throw new Error(`Filter file not found: ${resolved}`);
44
+ this.setFilterData(group, key, filter);
45
+ return filter;
46
+ }, `Failed to load filter at ${group}/${key}`);
47
+ }
48
+ saveFilter(group, key, list) {
49
+ log.debug(`Saving filter to ${group}/${key}`);
50
+ log.catch(() => {
51
+ const resolved = this.joinPath(group, key);
52
+ if (!resolved) throw new Error(`Invalid filter path: ${group}/${key}`);
53
+ const items = this.prepFilter(list);
54
+ const filter = { ...Utils.metaData(), items, count: items.length };
55
+ this.setFilterData(group, key, filter);
56
+ if (!Filter.storage.writeJSON(resolved, filter)) throw new Error(`Failed to write filter file: ${resolved}`);
57
+ }, `Failed to save filter at ${group}/${key}`);
58
+ }
59
+ saveGroup(group, data) {
60
+ Object.entries(data).forEach(([key, list]) => this.saveFilter(group, key, list));
61
+ }
62
+ saveSpecial(special, data) {
63
+ this.saveFilter('special', special, data);
64
+ }
65
+ // --- getter ---
66
+ getFilter(group, key) {
67
+ return this.data[group]?.[key] ?? this.loadFilter(group, key);
68
+ }
69
+ getFilterByPath(path) {
70
+ const [group, key] = this.splitPath(path) ?? [];
71
+ return group && key ? this.getFilter(group, key) : undefined;
72
+ }
73
+ getGroup(group) {
74
+ Filter.storage.scanDir(join('filter', group)).forEach(file => {
75
+ const key = file.replace('.json', '').split('/').pop();
76
+ if (key && !this.data[group]?.[key]) this.loadFilter(group, key);
77
+ });
78
+ return this.data[group];
79
+ }
80
+ getSpecial(special) {
81
+ Filter.storage.scanDir('filter/special').forEach(file => {
82
+ const key = file.replace('.json', '').split('/').pop();
83
+ if (key && !this.data.special?.[special]) this.loadFilter('special', special);
84
+ });
85
+ return this.data.special?.[special];
86
+ }
87
+ // --- has filter ---
88
+ has(path, uriLike) {
89
+ const [group, key] = this.splitPath(path) ?? [];
90
+ const uri = Utils.sanitize(uriLike);
91
+ return !!group && !!key && !!this.getFilter(group, key)?.items?.some(i => i.uri === uri);
92
+ }
93
+ // --- save (partial) filter collection ---
94
+ save(col) {
95
+ FilterGroup.forEach(g => g !== 'special' && col[g] && this.saveGroup(g, col[g]));
96
+ FilterSpecial.forEach(s => col.special?.[s] && this.saveSpecial(s, col.special[s]));
97
+ }
98
+ // --- instantiate ---
99
+ static getInstance() {
100
+ return (Filter.instance ??= new Filter());
101
+ }
102
+ // --- aggregate filter data ---
103
+ static aggregate(data, filter) {
104
+ const { uri, info, realtime } = data;
105
+ const item = { uri, name: info.name.shortName, value: undefined };
106
+ const decade = Parser.ageDecade(info.birthDate)?.toString();
107
+ const add = (g, k, v = null) => {
108
+ g && k && ((filter[g] ??= {})[k] ??= []) && filter[g][k].push({ ...item, value: v === null ? k : v });
109
+ };
110
+ add('industry', info.industry);
111
+ add('citizenship', info.citizenship);
112
+ add('country', info.residence?.country);
113
+ add('state', info.residence?.state);
114
+ add('gender', info.gender);
115
+ add('age', decade, info.birthDate);
116
+ add('maritalStatus', info.maritalStatus);
117
+ info.flags.deceased && add('special', 'deceased', undefined);
118
+ info.flags.dropOff && add('special', 'dropOff', realtime?.date);
119
+ info.flags.family && add('special', 'family', undefined);
120
+ info.selfMade?.is && add('special', 'selfMade', info.selfMade.rank);
121
+ }
122
+ }
@@ -0,0 +1,12 @@
1
+ import type { TListIndexItem, TListSnapshot, TListSnapshotData } from '@rtbnext/schema/src/model/list';
2
+ import { Snapshot } from '../abstract/Snapshot.js';
3
+ import type { IList } from '../interfaces/list';
4
+ export declare class List extends Snapshot<TListSnapshot> implements IList {
5
+ private static readonly index;
6
+ private readonly uri;
7
+ private constructor();
8
+ getUri(): string;
9
+ saveSnapshot(snapshot: TListSnapshotData, force?: boolean): boolean;
10
+ static get(uriLike: string): IList | false;
11
+ static create(uriLike: string, data: TListIndexItem, snapshot?: TListSnapshotData): List | false;
12
+ }
@@ -0,0 +1,43 @@
1
+ import { Snapshot } from '../abstract/Snapshot.js';
2
+ import { log } from '../core/Logger.js';
3
+ import { Utils } from '../core/Utils.js';
4
+ import { ListIndex } from './ListIndex.js';
5
+ export class List extends Snapshot {
6
+ static index = ListIndex.getInstance();
7
+ uri;
8
+ constructor(item) {
9
+ if (!item || !item.uri) throw new Error('No valid list index item given');
10
+ super('list');
11
+ this.uri = item.uri;
12
+ }
13
+ // --- getter ---
14
+ getUri() {
15
+ return this.uri;
16
+ }
17
+ // --- (override) save list snapshot ---
18
+ saveSnapshot(snapshot, force = false) {
19
+ return super.saveSnapshot({ ...Utils.metaData(), ...snapshot }, force);
20
+ }
21
+ // --- instantiate ---
22
+ static get(uriLike) {
23
+ try {
24
+ return new List(List.index.get(uriLike));
25
+ } catch {
26
+ return false;
27
+ }
28
+ }
29
+ // --- create list ---
30
+ static create(uriLike, data, snapshot) {
31
+ log.debug(`Creating List ${uriLike}`);
32
+ return (
33
+ log.catch(() => {
34
+ const item = List.index.add(uriLike, data);
35
+ if (!item) throw new Error(`List index item for ${uriLike} could not be created`);
36
+ const list = new List(item);
37
+ if (!list) throw new Error(`List ${item.uri} could not be created`);
38
+ if (snapshot) list.saveSnapshot(snapshot);
39
+ return list;
40
+ }, `Failed to create List ${uriLike}`) ?? false
41
+ );
42
+ }
43
+ }
@@ -0,0 +1,8 @@
1
+ import type { TListIndex, TListIndexItem } from '@rtbnext/schema/src/model/list';
2
+ import { Index } from '../abstract/Index.js';
3
+ import type { IListIndex } from '../interfaces/index';
4
+ export declare class ListIndex extends Index<TListIndexItem, TListIndex> implements IListIndex {
5
+ protected static instance: ListIndex;
6
+ private constructor();
7
+ static getInstance(): IListIndex;
8
+ }
@@ -0,0 +1,10 @@
1
+ import { Index } from '../abstract/Index.js';
2
+ export class ListIndex extends Index {
3
+ static instance;
4
+ constructor() {
5
+ super('list', 'list/index.json');
6
+ }
7
+ static getInstance() {
8
+ return (ListIndex.instance ??= new ListIndex());
9
+ }
10
+ }
@@ -0,0 +1,15 @@
1
+ import type { TRealtime } from '@rtbnext/schema/src/base/assets';
2
+ import type { TMover, TMoverData } from '@rtbnext/schema/src/model/mover';
3
+ import { Snapshot } from '../abstract/Snapshot.js';
4
+ import type { IMover } from '../interfaces/mover';
5
+ export declare class Mover extends Snapshot<TMover> implements IMover {
6
+ private static instance;
7
+ private constructor();
8
+ private prepItems;
9
+ private parseEntry;
10
+ private parseBucket;
11
+ saveSnapshot(snapshot: TMoverData, force?: boolean): boolean;
12
+ static getInstance(): IMover;
13
+ static factory(date?: any): TMoverData;
14
+ static aggregate(data: TRealtime | undefined, uri: string, name: string, mover: TMoverData, total?: number): void;
15
+ }