@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.
- package/LICENSE +21 -0
- package/README.md +11 -0
- package/dist/abstract/Cache.d.ts +9 -0
- package/dist/abstract/Cache.js +19 -0
- package/dist/abstract/Index.d.ts +22 -0
- package/dist/abstract/Index.js +81 -0
- package/dist/abstract/Job.d.ts +19 -0
- package/dist/abstract/Job.js +49 -0
- package/dist/abstract/Snapshot.d.ts +22 -0
- package/dist/abstract/Snapshot.js +78 -0
- package/dist/bin/cli.d.ts +2 -0
- package/dist/bin/cli.js +25 -0
- package/dist/bin/cron.d.ts +2 -0
- package/dist/bin/cron.js +4 -0
- package/dist/core/Config.d.ts +30 -0
- package/dist/core/Config.js +66 -0
- package/dist/core/Cron.d.ts +12 -0
- package/dist/core/Cron.js +52 -0
- package/dist/core/Fetch.d.ts +28 -0
- package/dist/core/Fetch.js +172 -0
- package/dist/core/Logger.d.ts +30 -0
- package/dist/core/Logger.js +92 -0
- package/dist/core/Queue.d.ts +37 -0
- package/dist/core/Queue.js +136 -0
- package/dist/core/Storage.d.ts +28 -0
- package/dist/core/Storage.js +166 -0
- package/dist/core/Utils.d.ts +33 -0
- package/dist/core/Utils.js +167 -0
- package/dist/interfaces/cache.d.ts +6 -0
- package/dist/interfaces/config.d.ts +21 -0
- package/dist/interfaces/cron.d.ts +3 -0
- package/dist/interfaces/fetch.d.ts +13 -0
- package/dist/interfaces/filter.d.ts +12 -0
- package/dist/interfaces/index.d.ts +30 -0
- package/dist/interfaces/job.d.ts +9 -0
- package/dist/interfaces/list.d.ts +9 -0
- package/dist/interfaces/logger.d.ts +20 -0
- package/dist/interfaces/mover.d.ts +7 -0
- package/dist/interfaces/parser.d.ts +68 -0
- package/dist/interfaces/profile.d.ts +30 -0
- package/dist/interfaces/queue.d.ts +17 -0
- package/dist/interfaces/snapshot.d.ts +16 -0
- package/dist/interfaces/stats.d.ts +45 -0
- package/dist/interfaces/storage.d.ts +16 -0
- package/dist/job/Alias.d.ts +8 -0
- package/dist/job/Alias.js +42 -0
- package/dist/job/Annual.d.ts +8 -0
- package/dist/job/Annual.js +41 -0
- package/dist/job/List.d.ts +11 -0
- package/dist/job/List.js +101 -0
- package/dist/job/Merge.d.ts +10 -0
- package/dist/job/Merge.js +59 -0
- package/dist/job/Move.d.ts +7 -0
- package/dist/job/Move.js +33 -0
- package/dist/job/Performance.d.ts +8 -0
- package/dist/job/Performance.js +27 -0
- package/dist/job/Profile.d.ts +11 -0
- package/dist/job/Profile.js +76 -0
- package/dist/job/Queue.d.ts +8 -0
- package/dist/job/Queue.js +54 -0
- package/dist/job/RTB.d.ts +12 -0
- package/dist/job/RTB.js +121 -0
- package/dist/job/Stats.d.ts +11 -0
- package/dist/job/Stats.js +46 -0
- package/dist/job/Top10.d.ts +9 -0
- package/dist/job/Top10.js +48 -0
- package/dist/job/Wiki.d.ts +9 -0
- package/dist/job/Wiki.js +40 -0
- package/dist/job/index.d.ts +26 -0
- package/dist/job/index.js +26 -0
- package/dist/lib/const.d.ts +31 -0
- package/dist/lib/const.js +74 -0
- package/dist/lib/list.d.ts +90 -0
- package/dist/lib/list.js +72 -0
- package/dist/lib/regex.d.ts +7 -0
- package/dist/lib/regex.js +7 -0
- package/dist/model/Filter.d.ts +28 -0
- package/dist/model/Filter.js +122 -0
- package/dist/model/List.d.ts +12 -0
- package/dist/model/List.js +43 -0
- package/dist/model/ListIndex.d.ts +8 -0
- package/dist/model/ListIndex.js +10 -0
- package/dist/model/Mover.d.ts +15 -0
- package/dist/model/Mover.js +74 -0
- package/dist/model/Profile.d.ts +49 -0
- package/dist/model/Profile.js +181 -0
- package/dist/model/ProfileIndex.d.ts +20 -0
- package/dist/model/ProfileIndex.js +140 -0
- package/dist/model/Stats.d.ts +56 -0
- package/dist/model/Stats.js +435 -0
- package/dist/parser/BillionairesListParser.d.ts +3 -0
- package/dist/parser/BillionairesListParser.js +2 -0
- package/dist/parser/ListParser.d.ts +7 -0
- package/dist/parser/ListParser.js +11 -0
- package/dist/parser/Parser.d.ts +43 -0
- package/dist/parser/Parser.js +146 -0
- package/dist/parser/PersonListParser.d.ts +29 -0
- package/dist/parser/PersonListParser.js +111 -0
- package/dist/parser/ProfileParser.d.ts +44 -0
- package/dist/parser/ProfileParser.js +193 -0
- package/dist/parser/RTBListParser.d.ts +15 -0
- package/dist/parser/RTBListParser.js +91 -0
- package/dist/types/annual.d.ts +7 -0
- package/dist/types/config.d.ts +35 -0
- package/dist/types/fetch.d.ts +3 -0
- package/dist/types/generic.d.ts +10 -0
- package/dist/types/job.d.ts +71 -0
- package/dist/types/list.d.ts +49 -0
- package/dist/types/parser.d.ts +7 -0
- package/dist/types/profile.d.ts +9 -0
- package/dist/types/queue.d.ts +15 -0
- package/dist/types/response.d.ts +183 -0
- package/dist/types/storage.d.ts +3 -0
- package/dist/types/wiki.d.ts +1 -0
- package/dist/utils/Annual.d.ts +7 -0
- package/dist/utils/Annual.js +99 -0
- package/dist/utils/Performance.d.ts +8 -0
- package/dist/utils/Performance.js +39 -0
- package/dist/utils/ProfileManager.d.ts +24 -0
- package/dist/utils/ProfileManager.js +60 -0
- package/dist/utils/ProfileMerger.d.ts +11 -0
- package/dist/utils/ProfileMerger.js +67 -0
- package/dist/utils/Ranking.d.ts +11 -0
- package/dist/utils/Ranking.js +77 -0
- package/dist/utils/Wiki.d.ts +11 -0
- package/dist/utils/Wiki.js +168 -0
- package/package.json +45 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
export type TResponse<T> = {
|
|
2
|
+
success: boolean;
|
|
3
|
+
data?: T;
|
|
4
|
+
error?: string;
|
|
5
|
+
statusCode?: number;
|
|
6
|
+
duration: number;
|
|
7
|
+
retries: number;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type TWaybackResponse = {
|
|
11
|
+
archived_snapshots: { closest?: { status: string; available: boolean; url: string; timestamp: string } };
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type TProfileResponse = {
|
|
15
|
+
person: {
|
|
16
|
+
naturalId: string;
|
|
17
|
+
name: string;
|
|
18
|
+
listImages?: Array<{
|
|
19
|
+
image: string;
|
|
20
|
+
uri: string;
|
|
21
|
+
caption: string;
|
|
22
|
+
description: string;
|
|
23
|
+
title: string;
|
|
24
|
+
credit: string;
|
|
25
|
+
}>;
|
|
26
|
+
geoLocation?: { longitude: number; latitude: number };
|
|
27
|
+
relatedEntities?: Array<{ name: string; type: string; uri?: string; relationshipType?: string }>;
|
|
28
|
+
uri: string;
|
|
29
|
+
dropOff: boolean;
|
|
30
|
+
uris: string[];
|
|
31
|
+
industries: string[];
|
|
32
|
+
embargo?: boolean;
|
|
33
|
+
firstName?: string;
|
|
34
|
+
lastName?: string;
|
|
35
|
+
birthDate?: number;
|
|
36
|
+
finalWorth: number;
|
|
37
|
+
finalWorthDate: number;
|
|
38
|
+
stateProvince?: string;
|
|
39
|
+
city?: string;
|
|
40
|
+
numberOfChildren?: number;
|
|
41
|
+
maritalStatus?: 'M' | 'F';
|
|
42
|
+
source: string;
|
|
43
|
+
title?: string;
|
|
44
|
+
organization?: string;
|
|
45
|
+
asianFormat?: 'N' | 'Y';
|
|
46
|
+
personLists: Array<{
|
|
47
|
+
listUri: string;
|
|
48
|
+
name: string;
|
|
49
|
+
listDescription?: string;
|
|
50
|
+
rank?: number;
|
|
51
|
+
finalWorth?: number;
|
|
52
|
+
timestamp: number | string;
|
|
53
|
+
financialAssets?: Array<{
|
|
54
|
+
exchange: string;
|
|
55
|
+
ticker: string;
|
|
56
|
+
companyName: string;
|
|
57
|
+
numberOfShares?: number;
|
|
58
|
+
sharePrice: number;
|
|
59
|
+
exchangeRate?: number;
|
|
60
|
+
currentPrice?: number;
|
|
61
|
+
}>;
|
|
62
|
+
date?: number | string;
|
|
63
|
+
estWorthPrev?: number;
|
|
64
|
+
privateAssetsWorth?: number;
|
|
65
|
+
familyList?: boolean;
|
|
66
|
+
archivedWorth?: number;
|
|
67
|
+
bios?: string[];
|
|
68
|
+
abouts?: string[];
|
|
69
|
+
philanthropyScore?: number;
|
|
70
|
+
}>;
|
|
71
|
+
educations?: Array<{ school: string; degree: string }>;
|
|
72
|
+
selfMade?: boolean;
|
|
73
|
+
gender: string;
|
|
74
|
+
countryOfCitizenship: string;
|
|
75
|
+
countryOfResidence?: string;
|
|
76
|
+
birthCity?: string;
|
|
77
|
+
birthState?: string;
|
|
78
|
+
birthCountry?: string;
|
|
79
|
+
deceased: boolean;
|
|
80
|
+
selfMadeType?: string;
|
|
81
|
+
selfMadeRank?: number;
|
|
82
|
+
quote?: string;
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export type TPersonListEntry = {
|
|
87
|
+
naturalId: string;
|
|
88
|
+
name: string;
|
|
89
|
+
listDescription?: string;
|
|
90
|
+
timestamp: number;
|
|
91
|
+
date?: number;
|
|
92
|
+
year: number;
|
|
93
|
+
listUri: string;
|
|
94
|
+
uri: string;
|
|
95
|
+
position?: number;
|
|
96
|
+
rank?: number;
|
|
97
|
+
previousRank?: number;
|
|
98
|
+
finalWorth?: number;
|
|
99
|
+
estWorthPrev?: number;
|
|
100
|
+
privateAssetsWorth?: number;
|
|
101
|
+
archivedWorth?: number;
|
|
102
|
+
change?: number;
|
|
103
|
+
changePercent?: number;
|
|
104
|
+
person?: { name?: string; uri?: string };
|
|
105
|
+
personName: string;
|
|
106
|
+
lastName?: string;
|
|
107
|
+
birthDate?: number;
|
|
108
|
+
gender?: 'M' | 'F';
|
|
109
|
+
countryOfCitizenship?: string;
|
|
110
|
+
country?: string;
|
|
111
|
+
state?: string;
|
|
112
|
+
city?: string;
|
|
113
|
+
source?: string;
|
|
114
|
+
industries?: string[];
|
|
115
|
+
selfMadeRank?: number;
|
|
116
|
+
philanthropyScore?: number;
|
|
117
|
+
organization?: string;
|
|
118
|
+
title?: string;
|
|
119
|
+
bios?: string[];
|
|
120
|
+
abouts?: string[];
|
|
121
|
+
financialAssets?: Array<{
|
|
122
|
+
exchange: string;
|
|
123
|
+
ticker: string;
|
|
124
|
+
companyName: string;
|
|
125
|
+
numberOfShares?: number;
|
|
126
|
+
sharePrice?: number;
|
|
127
|
+
currencyCode?: string;
|
|
128
|
+
exchangeRate?: number;
|
|
129
|
+
currentPrice?: number;
|
|
130
|
+
}>;
|
|
131
|
+
csfDisplayFields: string[];
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export type TListResponse<T extends object> = { personList: { count: number; personsLists: Array<T> } };
|
|
135
|
+
|
|
136
|
+
export type TWikidataProp =
|
|
137
|
+
'gender' | 'birthdate' | 'article' | 'image' | 'iso2' | 'occupation' | 'employer' | 'ownerOf' | 'netWorth';
|
|
138
|
+
|
|
139
|
+
export type TWikidataResponseItem = { item: { value: string }; itemLabel: { value: string; xmlLang: string } } & {
|
|
140
|
+
[K in TWikidataProp]?: { value: string };
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export type TWikidataResponse = { results: { bindings: TWikidataResponseItem[] } };
|
|
144
|
+
|
|
145
|
+
export type TWikipediaResponse = {
|
|
146
|
+
query: {
|
|
147
|
+
pages: Array<{
|
|
148
|
+
pageid: number;
|
|
149
|
+
title: string;
|
|
150
|
+
extract?: string;
|
|
151
|
+
touched: string;
|
|
152
|
+
lastrevid: number;
|
|
153
|
+
pageimage?: string;
|
|
154
|
+
pageprops?: { [K in 'defaultsort' | 'wikibase-shortdesc' | 'wikibase_item']?: string };
|
|
155
|
+
}>;
|
|
156
|
+
};
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
export type TCommonsResponse = {
|
|
160
|
+
query: {
|
|
161
|
+
pages: Array<{
|
|
162
|
+
imageinfo?: Array<{
|
|
163
|
+
url: string;
|
|
164
|
+
descriptionurl: string;
|
|
165
|
+
thumburl?: string;
|
|
166
|
+
responsiveUrls?: Record<string, string>;
|
|
167
|
+
extmetadata?: {
|
|
168
|
+
[
|
|
169
|
+
K in
|
|
170
|
+
| 'Artist'
|
|
171
|
+
| 'Attribution'
|
|
172
|
+
| 'Credit'
|
|
173
|
+
| 'DateTime'
|
|
174
|
+
| 'DateTimeOriginal'
|
|
175
|
+
| 'ImageDescription'
|
|
176
|
+
| 'LicenseShortName'
|
|
177
|
+
| 'UsageTerms'
|
|
178
|
+
]?: { value?: string };
|
|
179
|
+
};
|
|
180
|
+
}>;
|
|
181
|
+
}>;
|
|
182
|
+
};
|
|
183
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type TWikidata = { qid: string; confidence: number; article?: string; image?: string };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { log } from '../core/Logger.js';
|
|
2
|
+
import { Profile } from '../model/Profile.js';
|
|
3
|
+
import { ProfileIndex } from '../model/ProfileIndex.js';
|
|
4
|
+
import { Parser } from '../parser/Parser.js';
|
|
5
|
+
export class Annual {
|
|
6
|
+
static handler = {
|
|
7
|
+
rank: {
|
|
8
|
+
sort: (a, b) => a - b,
|
|
9
|
+
parse: v => Parser.number(v, 0),
|
|
10
|
+
flag: raw => {
|
|
11
|
+
if (raw.networth?.length && raw.networth[0] < 1000) return 'dropoff';
|
|
12
|
+
if (raw.prevRank === undefined) return raw.hadBefore ? 'returned' : 'new';
|
|
13
|
+
const diff = raw.prevRank - raw.rank[0];
|
|
14
|
+
return diff > 0 ? 'up' : diff < 0 ? 'down' : 'unchanged';
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
networth: {
|
|
18
|
+
sort: (a, b) => b - a,
|
|
19
|
+
parse: Parser.money,
|
|
20
|
+
flag: raw => {
|
|
21
|
+
if (raw.prevNetworth === undefined) return 'unknown';
|
|
22
|
+
const diff = raw.networth[0] - raw.prevNetworth;
|
|
23
|
+
return diff > 0 ? 'up' : diff < 0 ? 'down' : 'unchanged';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
static aggregate(history, year) {
|
|
28
|
+
const rank = [],
|
|
29
|
+
networth = [];
|
|
30
|
+
let prevRank = undefined;
|
|
31
|
+
let prevNetworth = undefined;
|
|
32
|
+
let hadBefore = false;
|
|
33
|
+
for (let i = history.length - 1; i >= 0; i--) {
|
|
34
|
+
const [date, rnk, ntw] = history[i];
|
|
35
|
+
const y = +date.slice(0, 4);
|
|
36
|
+
if (y > year) continue;
|
|
37
|
+
if (y === year - 1) {
|
|
38
|
+
prevRank ??= rnk;
|
|
39
|
+
prevNetworth ??= ntw;
|
|
40
|
+
}
|
|
41
|
+
if (y < year) hadBefore = true;
|
|
42
|
+
if (hadBefore) break;
|
|
43
|
+
if (y !== year) continue;
|
|
44
|
+
rank.push(rnk);
|
|
45
|
+
networth.push(ntw);
|
|
46
|
+
}
|
|
47
|
+
return { rank, networth, prevRank, prevNetworth, hadBefore };
|
|
48
|
+
}
|
|
49
|
+
static record(raw, type) {
|
|
50
|
+
const data = raw[type],
|
|
51
|
+
len = data?.length ?? 0;
|
|
52
|
+
if (len === 0) return;
|
|
53
|
+
const { sort, parse, flag } = Annual.handler[type];
|
|
54
|
+
const first = data[len - 1],
|
|
55
|
+
last = data[0];
|
|
56
|
+
const sorted = [...data].sort(sort);
|
|
57
|
+
const max = sorted[0],
|
|
58
|
+
min = sorted[len - 1];
|
|
59
|
+
const mean = sorted.reduce((sum, v) => sum + v, 0) / len;
|
|
60
|
+
const idx = Math.floor(len / 2);
|
|
61
|
+
const median = len % 2 === 0 ? (sorted[idx - 1] + sorted[idx]) / 2 : sorted[idx];
|
|
62
|
+
return {
|
|
63
|
+
first: parse(first),
|
|
64
|
+
last: parse(last),
|
|
65
|
+
max: parse(max),
|
|
66
|
+
min: parse(min),
|
|
67
|
+
diff: parse(sort(first, last)),
|
|
68
|
+
flag: flag(raw),
|
|
69
|
+
mean: parse(mean),
|
|
70
|
+
median: parse(median),
|
|
71
|
+
range: parse(Math.abs(max - min)),
|
|
72
|
+
stdDev: parse(Math.sqrt(sorted.reduce((sum, v) => sum + (v - mean) ** 2, 0) / len))
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
static generate(uriLike, year) {
|
|
76
|
+
return (
|
|
77
|
+
log.catch(() => {
|
|
78
|
+
const profile = Profile.get(uriLike);
|
|
79
|
+
if (!profile) throw new Error(`Profile not found for ${uriLike}`);
|
|
80
|
+
const history = profile.getHistory();
|
|
81
|
+
if (!history?.length) throw new Error(`No history found for ${uriLike}`);
|
|
82
|
+
const raw = Annual.aggregate(history, year);
|
|
83
|
+
const item = { year, rank: Annual.record(raw, 'rank'), networth: Annual.record(raw, 'networth') };
|
|
84
|
+
const annual = profile.getData().annual ?? [];
|
|
85
|
+
const idx = annual.findIndex(a => a.year === year);
|
|
86
|
+
if (idx >= 0) annual[idx] = item;
|
|
87
|
+
else annual.push(item);
|
|
88
|
+
profile.updateData({ annual });
|
|
89
|
+
profile.save();
|
|
90
|
+
return true;
|
|
91
|
+
}, `Failed to generate annual report for ${uriLike} @ ${year}`) ?? false
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
static generateAll(year) {
|
|
95
|
+
let count = 0;
|
|
96
|
+
for (const uri of ProfileIndex.getInstance().keys) if (Annual.generate(uri, year)) count++;
|
|
97
|
+
return count;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { TExtrema, TPerformance, TReturns } from '@rtbnext/schema/src/base/assets';
|
|
2
|
+
import type { TProfileHistory } from '@rtbnext/schema/src/model/profile';
|
|
3
|
+
export declare class Performance {
|
|
4
|
+
private static readonly RETURNS;
|
|
5
|
+
static getProfileExtrema(history: TProfileHistory): TExtrema;
|
|
6
|
+
static generateProfileReturns(history: TProfileHistory): TReturns;
|
|
7
|
+
static generateProfilePerformance(history: TProfileHistory): TPerformance;
|
|
8
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Parser } from '../parser/Parser.js';
|
|
2
|
+
export class Performance {
|
|
3
|
+
static RETURNS = { week: 7, month: 30, quarter: 90, halfYear: 182, year: 365, twoYear: 730, fiveYear: 1825 };
|
|
4
|
+
// --- extrema ---
|
|
5
|
+
static getProfileExtrema(history) {
|
|
6
|
+
const map = ([date, rank, networth]) => ({ date, rank: Parser.number(rank), networth: Parser.money(networth) });
|
|
7
|
+
const [low, high] = history.reduce(
|
|
8
|
+
([l, h], row) => [!l || row[2] < l[2] ? row : l, !h || row[2] > h[2] ? row : h],
|
|
9
|
+
[]
|
|
10
|
+
);
|
|
11
|
+
return { low: low && map(low), high: high && map(high) };
|
|
12
|
+
}
|
|
13
|
+
// --- returns ---
|
|
14
|
+
static generateProfileReturns(history) {
|
|
15
|
+
const latest = history.at(-1);
|
|
16
|
+
if (!latest) return {};
|
|
17
|
+
const result = {};
|
|
18
|
+
const now = Date.parse(latest[0]);
|
|
19
|
+
const networth = latest[2];
|
|
20
|
+
const returns = Object.entries(Performance.RETURNS);
|
|
21
|
+
let remaining = Object.keys(returns).length;
|
|
22
|
+
for (const item of history.toReversed()) {
|
|
23
|
+
const days = (now - Date.parse(item[0])) / 86400000;
|
|
24
|
+
for (const [key, target] of returns) {
|
|
25
|
+
if (result[key] || days < target) continue;
|
|
26
|
+
result[key] = {
|
|
27
|
+
value: Parser.money(networth - item[2]),
|
|
28
|
+
percent: Parser.pct((networth / (item[2] ?? 1)) * 100)
|
|
29
|
+
};
|
|
30
|
+
if (--remaining === 0) return result;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
// --- performance ---
|
|
36
|
+
static generateProfilePerformance(history) {
|
|
37
|
+
return { extrema: Performance.getProfileExtrema(history), returns: Performance.generateProfileReturns(history) };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { TProfileData } from '@rtbnext/schema/src/model/profile';
|
|
2
|
+
import type { IProfile } from '../interfaces/profile';
|
|
3
|
+
import type {
|
|
4
|
+
TProfileUpdateMode,
|
|
5
|
+
TProfileLookupResult,
|
|
6
|
+
TProfileOperation,
|
|
7
|
+
TProfileProcessResult
|
|
8
|
+
} from '../types/profile';
|
|
9
|
+
import type { TQueueOptions } from '../types/queue';
|
|
10
|
+
export declare class ProfileManager {
|
|
11
|
+
private static readonly index;
|
|
12
|
+
private static execute;
|
|
13
|
+
static lookup(uriLike: string, id?: string, profileData?: Partial<TProfileData>): TProfileLookupResult;
|
|
14
|
+
static determineAction(lookup: TProfileLookupResult): TProfileOperation;
|
|
15
|
+
static process(
|
|
16
|
+
uriLike: string,
|
|
17
|
+
id: string,
|
|
18
|
+
profileData: Partial<TProfileData>,
|
|
19
|
+
mode?: TProfileUpdateMode,
|
|
20
|
+
makeAlias?: boolean,
|
|
21
|
+
touchLookup?: boolean
|
|
22
|
+
): TProfileProcessResult;
|
|
23
|
+
static updateQueue(queue: TQueueOptions[], profile: IProfile, action: TProfileOperation, th?: number): void;
|
|
24
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Profile } from '../model/Profile.js';
|
|
2
|
+
import { ProfileIndex } from '../model/ProfileIndex.js';
|
|
3
|
+
import { ProfileMerger } from './ProfileMerger.js';
|
|
4
|
+
export class ProfileManager {
|
|
5
|
+
static index = ProfileIndex.getInstance();
|
|
6
|
+
static execute(profile, action, uriLike, profileData, mode = 'updateData', makeAlias = true, touchLookup = false) {
|
|
7
|
+
if (!profile) return Profile.create(uriLike, profileData);
|
|
8
|
+
if (touchLookup) profile.touchLookup();
|
|
9
|
+
switch (action) {
|
|
10
|
+
case 'update':
|
|
11
|
+
if (mode !== 'createOnly') {
|
|
12
|
+
profile[mode](profileData);
|
|
13
|
+
profile.save();
|
|
14
|
+
}
|
|
15
|
+
break;
|
|
16
|
+
case 'move':
|
|
17
|
+
if (mode !== 'createOnly') {
|
|
18
|
+
profile[mode](profileData);
|
|
19
|
+
profile.move(uriLike, makeAlias);
|
|
20
|
+
} else if (makeAlias) {
|
|
21
|
+
this.index.addAliases(profile.getUri(), uriLike);
|
|
22
|
+
}
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
return profile;
|
|
26
|
+
}
|
|
27
|
+
// --- lookup profile by URI and ID, or find a similar matching profile ---
|
|
28
|
+
static lookup(uriLike, id, profileData) {
|
|
29
|
+
let profile = Profile.find(uriLike);
|
|
30
|
+
const isExisting = profile && profile.verify(id ?? '');
|
|
31
|
+
const isSimilar = !isExisting && !!(profile = ProfileMerger.findMatching(profileData ?? {})[0]);
|
|
32
|
+
return { profile, isExisting, isSimilar };
|
|
33
|
+
}
|
|
34
|
+
// --- determine action based on profile lookup ---
|
|
35
|
+
static determineAction(lookup) {
|
|
36
|
+
return lookup.isExisting ? 'update' : lookup.isSimilar ? 'move' : 'create';
|
|
37
|
+
}
|
|
38
|
+
// --- perform profile operation ---
|
|
39
|
+
static process(uriLike, id, profileData, mode = 'updateData', makeAlias = true, touchLookup = false) {
|
|
40
|
+
const lookup = this.lookup(uriLike, id, profileData);
|
|
41
|
+
const action = this.determineAction(lookup);
|
|
42
|
+
const profile = this.execute(lookup.profile, action, uriLike, profileData, mode, makeAlias, touchLookup);
|
|
43
|
+
return { profile, action, success: !!profile };
|
|
44
|
+
}
|
|
45
|
+
// --- add queue item based on action ---
|
|
46
|
+
static updateQueue(queue, profile, action, th) {
|
|
47
|
+
const uriLike = profile.getUri();
|
|
48
|
+
switch (action) {
|
|
49
|
+
case 'update':
|
|
50
|
+
if (!th || (profile.lastLookupTime() ?? 0) < th) queue.push({ uriLike });
|
|
51
|
+
break;
|
|
52
|
+
case 'move':
|
|
53
|
+
queue.push({ uriLike, prio: 5 });
|
|
54
|
+
break;
|
|
55
|
+
case 'create':
|
|
56
|
+
queue.push({ uriLike, prio: 10 });
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { TProfileData } from '@rtbnext/schema/src/model/profile';
|
|
2
|
+
import type { IProfile } from '../interfaces/profile';
|
|
3
|
+
export declare class ProfileMerger {
|
|
4
|
+
private static readonly cmp;
|
|
5
|
+
private static readonly index;
|
|
6
|
+
private static similarURIs;
|
|
7
|
+
static mergeableProfiles(target: Partial<TProfileData>, source: Partial<TProfileData>): boolean;
|
|
8
|
+
static findMatching(data: Partial<TProfileData>): IProfile[];
|
|
9
|
+
static mergeProfiles(target: IProfile, source: IProfile, force?: boolean, makeAlias?: boolean): boolean;
|
|
10
|
+
static listCandidates(...uriLike: string[]): Record<string, string[]>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { CmpStrAsync } from 'cmpstr';
|
|
2
|
+
import { REGEX_URI_CLEANUP } from '../lib/regex.js';
|
|
3
|
+
import { Profile } from '../model/Profile.js';
|
|
4
|
+
import { ProfileIndex } from '../model/ProfileIndex.js';
|
|
5
|
+
CmpStrAsync.filter.add('input', 'normalizeUri', uri => uri.replace(REGEX_URI_CLEANUP, ''));
|
|
6
|
+
export class ProfileMerger {
|
|
7
|
+
static cmp = CmpStrAsync.create({ metric: 'dice', safeEmpty: true });
|
|
8
|
+
static index = ProfileIndex.getInstance();
|
|
9
|
+
// --- helper ---
|
|
10
|
+
static similarURIs(uri) {
|
|
11
|
+
const revUri = uri.split('-').reverse().join('-');
|
|
12
|
+
const keys = [...ProfileMerger.index.keys];
|
|
13
|
+
const res = new Set([
|
|
14
|
+
...ProfileMerger.cmp.match(keys, uri, 0.9).map(i => i.source),
|
|
15
|
+
...ProfileMerger.cmp.match(keys, revUri, 0.8).map(i => i.source)
|
|
16
|
+
]);
|
|
17
|
+
res.delete(uri);
|
|
18
|
+
return [...res];
|
|
19
|
+
}
|
|
20
|
+
// --- check mergeable profiles ---
|
|
21
|
+
static mergeableProfiles(target, source) {
|
|
22
|
+
if (target.id === source.id) return true;
|
|
23
|
+
for (const match of ['gender', 'birthDate', 'birthPlace', 'citizenship', 'industry'])
|
|
24
|
+
if (
|
|
25
|
+
target.info &&
|
|
26
|
+
match in target.info &&
|
|
27
|
+
source.info &&
|
|
28
|
+
match in source.info &&
|
|
29
|
+
JSON.stringify(target.info[match]) !== JSON.stringify(source.info[match])
|
|
30
|
+
)
|
|
31
|
+
return false;
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
// --- find matching profiles ---
|
|
35
|
+
static findMatching(data) {
|
|
36
|
+
if (!data.id || !data.uri) return [];
|
|
37
|
+
const res = [];
|
|
38
|
+
for (const uri of ProfileMerger.similarURIs(data.uri)) {
|
|
39
|
+
const profile = Profile.get(uri);
|
|
40
|
+
if (profile && ProfileMerger.mergeableProfiles(profile.getData(), data)) res.push(profile);
|
|
41
|
+
}
|
|
42
|
+
return res;
|
|
43
|
+
}
|
|
44
|
+
// --- merge profiles ---
|
|
45
|
+
static mergeProfiles(target, source, force = false, makeAlias = true) {
|
|
46
|
+
if (!force && !ProfileMerger.mergeableProfiles(target.getData(), source.getData())) return false;
|
|
47
|
+
target.updateData(source.getData(), 'unique' /* ArrayMode.Unique */);
|
|
48
|
+
target.mergeHistory(source.getHistory());
|
|
49
|
+
target.save();
|
|
50
|
+
return (
|
|
51
|
+
Profile.delete(source.getUri()) &&
|
|
52
|
+
(!makeAlias ? true : !!ProfileMerger.index.addAliases(target.getUri(), source.getUri()))
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
// --- list matching candidates ---
|
|
56
|
+
static listCandidates(...uriLike) {
|
|
57
|
+
if (!uriLike.length) return {};
|
|
58
|
+
const res = {};
|
|
59
|
+
for (const uri of uriLike) {
|
|
60
|
+
const profile = Profile.get(uri);
|
|
61
|
+
if (!profile) continue;
|
|
62
|
+
const matches = this.findMatching(profile.getData());
|
|
63
|
+
res[profile.getUri()] = matches.map(m => m.getUri());
|
|
64
|
+
}
|
|
65
|
+
return res;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { TRanking } from '@rtbnext/schema/src/base/assets';
|
|
2
|
+
import type { TProfileResponse } from '../types/response';
|
|
3
|
+
export declare class Ranking {
|
|
4
|
+
private static readonly queue;
|
|
5
|
+
static generateProfileRanking(
|
|
6
|
+
sortedLists: TProfileResponse['person']['personLists'],
|
|
7
|
+
rankingData?: TRanking[],
|
|
8
|
+
history?: boolean,
|
|
9
|
+
addQueue?: boolean
|
|
10
|
+
): TRanking[];
|
|
11
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { ListQueue } from '../core/Queue.js';
|
|
2
|
+
import { List } from '../model/List.js';
|
|
3
|
+
import { Parser } from '../parser/Parser.js';
|
|
4
|
+
export class Ranking {
|
|
5
|
+
static queue = ListQueue.getInstance();
|
|
6
|
+
static generateProfileRanking(sortedLists, rankingData = [], history = true, addQueue = true) {
|
|
7
|
+
const lists = new Map(rankingData.map(r => [r.list, r]));
|
|
8
|
+
const entries = new Map();
|
|
9
|
+
const names = new Map();
|
|
10
|
+
const queue = [];
|
|
11
|
+
// --- prep new entries from sorted lists ---
|
|
12
|
+
for (const { listUri, name, listDescription, date, timestamp, rank, finalWorth } of sortedLists) {
|
|
13
|
+
if (['rtb', 'rtrl'].includes(listUri)) continue;
|
|
14
|
+
const item = Parser.container({
|
|
15
|
+
date: { value: date ?? timestamp, type: 'date', args: ['ymd'], strict: false },
|
|
16
|
+
rank: { value: rank, type: 'number' },
|
|
17
|
+
networth: { value: finalWorth, type: 'money' }
|
|
18
|
+
});
|
|
19
|
+
if (!entries.has(listUri)) entries.set(listUri, []);
|
|
20
|
+
entries.get(listUri).push(item);
|
|
21
|
+
names.set(listUri, { name, desc: listDescription });
|
|
22
|
+
}
|
|
23
|
+
// --- merge existing and new entries ---
|
|
24
|
+
const allListUris = new Set([...lists.keys(), ...entries.keys()]);
|
|
25
|
+
const result = [];
|
|
26
|
+
// --- generate final rankings ---
|
|
27
|
+
for (const listUri of allListUris) {
|
|
28
|
+
const existing = lists.get(listUri);
|
|
29
|
+
const news = entries.get(listUri) ?? [];
|
|
30
|
+
const allItems = [];
|
|
31
|
+
// --- add existing entry ---
|
|
32
|
+
if (existing) {
|
|
33
|
+
allItems.push({
|
|
34
|
+
date: existing.date,
|
|
35
|
+
rank: existing.rank,
|
|
36
|
+
networth: existing.networth,
|
|
37
|
+
prev: existing.prev,
|
|
38
|
+
next: existing.next
|
|
39
|
+
});
|
|
40
|
+
if (existing.history) allItems.push(...existing.history);
|
|
41
|
+
}
|
|
42
|
+
// --- add new entries and sort by date ---
|
|
43
|
+
allItems.push(...news);
|
|
44
|
+
allItems.sort((a, b) => b.date.localeCompare(a.date));
|
|
45
|
+
const main = allItems[0];
|
|
46
|
+
let historyItems = [];
|
|
47
|
+
// --- prepare history ---
|
|
48
|
+
if (history) historyItems = allItems.slice(1);
|
|
49
|
+
else if (existing?.history) historyItems = [...existing.history];
|
|
50
|
+
historyItems.sort((a, b) => b.date.localeCompare(a.date));
|
|
51
|
+
// --- create final ranking entry ---
|
|
52
|
+
const name = existing?.name ?? names.get(listUri)?.name ?? listUri;
|
|
53
|
+
const ranking = {
|
|
54
|
+
list: listUri,
|
|
55
|
+
name,
|
|
56
|
+
date: main.date,
|
|
57
|
+
rank: main.rank,
|
|
58
|
+
prev: main.prev,
|
|
59
|
+
next: main.next,
|
|
60
|
+
networth: main.networth,
|
|
61
|
+
history: historyItems
|
|
62
|
+
};
|
|
63
|
+
result.push(ranking);
|
|
64
|
+
// --- queue list for future processing if needed ---
|
|
65
|
+
if (addQueue && main.rank && main.networth) {
|
|
66
|
+
const list = List.get(listUri);
|
|
67
|
+
if (!list || !list.hasDate(main.date))
|
|
68
|
+
queue.push({
|
|
69
|
+
uriLike: listUri,
|
|
70
|
+
args: { name, desc: names.get(listUri)?.desc, year: main.date.split('-')[0] }
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (addQueue && queue.length) this.queue.addMany(queue);
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { TImage, TWiki } from '@rtbnext/schema/src/base/generic';
|
|
2
|
+
import type { TProfileData } from '@rtbnext/schema/src/model/profile';
|
|
3
|
+
import type { TWikidata } from '../types/wiki';
|
|
4
|
+
export declare class Wiki {
|
|
5
|
+
private static readonly fetch;
|
|
6
|
+
private static scoreWDItem;
|
|
7
|
+
static queryWikidata(data: Partial<TProfileData>): Promise<TWikidata | undefined>;
|
|
8
|
+
static queryCommonsImage(title: string): Promise<TImage | undefined>;
|
|
9
|
+
static queryWikiPage(title: string, qid?: string, image?: TImage, confidence?: number): Promise<TWiki | undefined>;
|
|
10
|
+
static fromProfileData(data: Partial<TProfileData>): Promise<TWiki | undefined>;
|
|
11
|
+
}
|