@proconnect-gouv/proconnect.identite 1.1.0 → 1.2.0
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/CHANGELOG.md +6 -0
- package/dist/data/certification/commune-code-conversion.d.ts +13 -0
- package/dist/data/certification/commune-code-conversion.d.ts.map +1 -0
- package/dist/data/certification/commune-code-conversion.js +5188 -0
- package/dist/data/certification/country-iso-to-cog.d.ts +12 -0
- package/dist/data/certification/country-iso-to-cog.d.ts.map +1 -0
- package/dist/data/certification/country-iso-to-cog.js +438 -0
- package/dist/data/certification/index.d.ts +3 -0
- package/dist/data/certification/index.d.ts.map +1 -0
- package/dist/data/certification/index.js +3 -0
- package/dist/{mappers/certification/index.d.ts → managers/certification/adapters/api_entreprise.d.ts} +2 -2
- package/dist/managers/certification/adapters/api_entreprise.d.ts.map +1 -0
- package/dist/managers/certification/adapters/api_entreprise.js +14 -0
- package/dist/managers/certification/adapters/franceconnect.d.ts +3 -0
- package/dist/managers/certification/adapters/franceconnect.d.ts.map +1 -0
- package/dist/managers/certification/adapters/franceconnect.js +15 -0
- package/dist/managers/certification/adapters/insee.d.ts +4 -0
- package/dist/managers/certification/adapters/insee.d.ts.map +1 -0
- package/dist/managers/certification/adapters/insee.js +29 -0
- package/dist/managers/certification/adapters/rne.d.ts +4 -0
- package/dist/managers/certification/adapters/rne.d.ts.map +1 -0
- package/dist/managers/certification/adapters/rne.js +45 -0
- package/dist/managers/certification/birthplace-conversion.d.ts +27 -0
- package/dist/managers/certification/birthplace-conversion.d.ts.map +1 -0
- package/dist/managers/certification/birthplace-conversion.js +45 -0
- package/dist/managers/certification/certification-score.d.ts +22 -0
- package/dist/managers/certification/certification-score.d.ts.map +1 -0
- package/dist/managers/certification/certification-score.js +72 -0
- package/dist/managers/certification/index.d.ts +1 -1
- package/dist/managers/certification/index.d.ts.map +1 -1
- package/dist/managers/certification/index.js +1 -1
- package/dist/managers/certification/is-organization-dirigeant.d.ts +38 -8
- package/dist/managers/certification/is-organization-dirigeant.d.ts.map +1 -1
- package/dist/managers/certification/is-organization-dirigeant.js +89 -52
- package/dist/managers/certification/normalize.d.ts +21 -0
- package/dist/managers/certification/normalize.d.ts.map +1 -0
- package/dist/managers/certification/normalize.js +64 -0
- package/dist/managers/franceconnect/openid-client.d.ts +1 -0
- package/dist/managers/franceconnect/openid-client.d.ts.map +1 -1
- package/dist/managers/organization/adapters/api_entreprise.d.ts +25 -0
- package/dist/managers/organization/adapters/api_entreprise.d.ts.map +1 -0
- package/dist/{mappers/organization/from-siret.js → managers/organization/adapters/api_entreprise.js} +25 -2
- package/dist/managers/organization/get-organization-info.d.ts.map +1 -1
- package/dist/managers/organization/get-organization-info.js +3 -3
- package/dist/repositories/organization/upsert.d.ts +1 -1
- package/dist/repositories/organization/upsert.d.ts.map +1 -1
- package/dist/repositories/organization/upsert.js +26 -1
- package/dist/repositories/user/get-franceconnect-user-info.d.ts +1 -0
- package/dist/repositories/user/get-franceconnect-user-info.d.ts.map +1 -1
- package/dist/repositories/user/upsert-franceconnect-userinfo.d.ts +1 -0
- package/dist/repositories/user/upsert-franceconnect-userinfo.d.ts.map +1 -1
- package/dist/services/organization/is-syndicat-communal.d.ts.map +1 -1
- package/dist/services/organization/is-syndicat-communal.js +2 -0
- package/dist/types/dirigeant.d.ts +5 -0
- package/dist/types/dirigeant.d.ts.map +1 -1
- package/dist/types/dirigeant.js +2 -0
- package/dist/types/franceconnect.d.ts +2 -0
- package/dist/types/franceconnect.d.ts.map +1 -1
- package/dist/types/franceconnect.js +1 -0
- package/dist/types/organization.d.ts +2 -2
- package/dist/types/organization.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/data/certification/commune-code-conversion.ts +5189 -0
- package/src/data/certification/country-iso-to-cog.ts +439 -0
- package/src/data/certification/index.ts +4 -0
- package/src/managers/certification/adapters/api_entreprise.test.ts +68 -0
- package/src/managers/certification/adapters/api_entreprise.test.ts.snapshot +109 -0
- package/src/{mappers/certification/index.ts → managers/certification/adapters/api_entreprise.ts} +7 -4
- package/src/managers/certification/adapters/franceconnect.ts +21 -0
- package/src/managers/certification/adapters/insee.test.ts +18 -0
- package/src/managers/certification/adapters/insee.test.ts.snapshot +21 -0
- package/src/managers/certification/adapters/insee.ts +39 -0
- package/src/managers/certification/adapters/rne.test.ts +276 -0
- package/src/managers/certification/adapters/rne.ts +64 -0
- package/src/managers/certification/birthplace-conversion.test.ts +76 -0
- package/src/managers/certification/birthplace-conversion.ts +58 -0
- package/src/managers/certification/certification-score.test.ts +309 -0
- package/src/managers/certification/certification-score.ts +97 -0
- package/src/managers/certification/index.ts +1 -1
- package/src/managers/certification/is-organization-dirigeant.test.ts +139 -48
- package/src/managers/certification/is-organization-dirigeant.ts +132 -106
- package/src/managers/certification/normalize.test.ts +71 -0
- package/src/managers/certification/normalize.ts +72 -0
- package/src/managers/organization/adapters/api_entreprise.test.ts +31 -0
- package/src/{mappers/organization/from-siret.test.ts.snapshot → managers/organization/adapters/api_entreprise.test.ts.snapshot} +26 -3
- package/src/{mappers/organization/from-siret.ts → managers/organization/adapters/api_entreprise.ts} +53 -3
- package/src/managers/organization/get-organization-info.test.ts +1 -1
- package/src/managers/organization/get-organization-info.ts +3 -3
- package/src/repositories/organization/upsert.ts +69 -19
- package/src/repositories/user/get-franceconnect-user-info.test.ts +1 -0
- package/src/repositories/user/upsert-franceconnect-userinfo.test.ts +2 -0
- package/src/services/organization/is-syndicat-communal.ts +2 -0
- package/src/types/dirigeant.ts +3 -0
- package/src/types/franceconnect.ts +1 -0
- package/src/types/organization.ts +2 -2
- package/testing/seed/franceconnect/index.ts +12 -9
- package/testing/seed/organizations/index.ts +34 -0
- package/tsconfig.json +5 -2
- package/tsconfig.lib.tsbuildinfo +1 -1
- package/dist/managers/certification/distance.d.ts +0 -4
- package/dist/managers/certification/distance.d.ts.map +0 -1
- package/dist/managers/certification/distance.js +0 -16
- package/dist/mappers/certification/index.d.ts.map +0 -1
- package/dist/mappers/certification/index.js +0 -11
- package/dist/mappers/index.d.ts +0 -2
- package/dist/mappers/index.d.ts.map +0 -1
- package/dist/mappers/index.js +0 -2
- package/dist/mappers/organization/from-siret.d.ts +0 -5
- package/dist/mappers/organization/from-siret.d.ts.map +0 -1
- package/dist/mappers/organization/index.d.ts +0 -2
- package/dist/mappers/organization/index.d.ts.map +0 -1
- package/dist/mappers/organization/index.js +0 -2
- package/src/managers/certification/distance.test.ts +0 -109
- package/src/managers/certification/distance.ts +0 -41
- package/src/mappers/index.ts +0 -3
- package/src/mappers/organization/from-siret.test.ts +0 -26
- package/src/mappers/organization/index.ts +0 -3
- package/testing/seed/insee/index.ts +0 -22
- package/testing/seed/mandataires/index.ts +0 -32
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
//
|
|
2
|
+
|
|
3
|
+
import assert from "node:assert/strict";
|
|
4
|
+
import { describe, it } from "node:test";
|
|
5
|
+
import { certificationScore } from "./certification-score.js";
|
|
6
|
+
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
describe("certification scoring", () => {
|
|
10
|
+
it("perfect match - all 5 criteria", () => {
|
|
11
|
+
// All criteria match: family name, first name, gender, birthdate, birthplace
|
|
12
|
+
assert.equal(
|
|
13
|
+
certificationScore(
|
|
14
|
+
{
|
|
15
|
+
birthcountry: null,
|
|
16
|
+
birthdate: new Date("1946-08-17"),
|
|
17
|
+
birthplace: "75001",
|
|
18
|
+
family_name: "Bernard",
|
|
19
|
+
gender: "male",
|
|
20
|
+
given_name: "Stéphane",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
birthcountry: null,
|
|
24
|
+
birthdate: new Date("1946-08-17"),
|
|
25
|
+
birthplace: "75001",
|
|
26
|
+
family_name: "Bernard",
|
|
27
|
+
gender: "male",
|
|
28
|
+
given_name: "Stéphane",
|
|
29
|
+
},
|
|
30
|
+
),
|
|
31
|
+
5,
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("family name mismatch", () => {
|
|
36
|
+
// Different family name = -1 point
|
|
37
|
+
assert.equal(
|
|
38
|
+
certificationScore(
|
|
39
|
+
{
|
|
40
|
+
birthcountry: null,
|
|
41
|
+
birthdate: new Date("1946-08-17"),
|
|
42
|
+
birthplace: "75001",
|
|
43
|
+
family_name: "DuMoulin",
|
|
44
|
+
gender: "male",
|
|
45
|
+
given_name: "Stéphane",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
birthcountry: null,
|
|
49
|
+
birthdate: new Date("1946-08-17"),
|
|
50
|
+
birthplace: "75001",
|
|
51
|
+
family_name: "Bernard",
|
|
52
|
+
gender: "male",
|
|
53
|
+
given_name: "Stéphane",
|
|
54
|
+
},
|
|
55
|
+
),
|
|
56
|
+
4,
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("different birthdate", () => {
|
|
61
|
+
// Different birthdate = -1 point
|
|
62
|
+
assert.equal(
|
|
63
|
+
certificationScore(
|
|
64
|
+
{
|
|
65
|
+
birthcountry: null,
|
|
66
|
+
birthdate: new Date("1946-08-17"),
|
|
67
|
+
birthplace: "75001",
|
|
68
|
+
family_name: "Bernard",
|
|
69
|
+
gender: "male",
|
|
70
|
+
given_name: "Stéphane",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
birthcountry: null,
|
|
74
|
+
birthdate: new Date("1986-08-17"),
|
|
75
|
+
birthplace: "75001",
|
|
76
|
+
family_name: "Bernard",
|
|
77
|
+
gender: "male",
|
|
78
|
+
given_name: "Stéphane",
|
|
79
|
+
},
|
|
80
|
+
),
|
|
81
|
+
4,
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("different birthplace", () => {
|
|
86
|
+
// Different birthplace = -1 point
|
|
87
|
+
assert.equal(
|
|
88
|
+
certificationScore(
|
|
89
|
+
{
|
|
90
|
+
birthcountry: null,
|
|
91
|
+
birthdate: new Date("1946-08-17"),
|
|
92
|
+
birthplace: "75001",
|
|
93
|
+
family_name: "Bernard",
|
|
94
|
+
gender: "male",
|
|
95
|
+
given_name: "Stéphane",
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
birthcountry: null,
|
|
99
|
+
birthdate: new Date("1946-08-17"),
|
|
100
|
+
birthplace: "13001",
|
|
101
|
+
family_name: "Bernard",
|
|
102
|
+
gender: "male",
|
|
103
|
+
given_name: "Stéphane",
|
|
104
|
+
},
|
|
105
|
+
),
|
|
106
|
+
4,
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("swapped first and family names", () => {
|
|
111
|
+
// Both names don't match = -2 points
|
|
112
|
+
assert.equal(
|
|
113
|
+
certificationScore(
|
|
114
|
+
{
|
|
115
|
+
birthcountry: null,
|
|
116
|
+
birthdate: new Date("1946-08-17"),
|
|
117
|
+
birthplace: "75001",
|
|
118
|
+
family_name: "Stéphane",
|
|
119
|
+
gender: "male",
|
|
120
|
+
given_name: "Bernard",
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
birthcountry: null,
|
|
124
|
+
birthdate: new Date("1946-08-17"),
|
|
125
|
+
birthplace: "75001",
|
|
126
|
+
family_name: "Bernard",
|
|
127
|
+
gender: "male",
|
|
128
|
+
given_name: "Stéphane",
|
|
129
|
+
},
|
|
130
|
+
),
|
|
131
|
+
3,
|
|
132
|
+
);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("different gender still matches when source gender is null", () => {
|
|
136
|
+
// Gender is null in source = still gets point
|
|
137
|
+
assert.equal(
|
|
138
|
+
certificationScore(
|
|
139
|
+
{
|
|
140
|
+
birthcountry: null,
|
|
141
|
+
birthdate: new Date("1946-08-17"),
|
|
142
|
+
birthplace: "75001",
|
|
143
|
+
family_name: "Bernard",
|
|
144
|
+
gender: "male",
|
|
145
|
+
given_name: "Stéphane",
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
birthcountry: null,
|
|
149
|
+
birthdate: new Date("1946-08-17"),
|
|
150
|
+
birthplace: "75001",
|
|
151
|
+
family_name: "Bernard",
|
|
152
|
+
gender: null,
|
|
153
|
+
given_name: "Stéphane",
|
|
154
|
+
},
|
|
155
|
+
),
|
|
156
|
+
5,
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("different gender when source has gender", () => {
|
|
161
|
+
// Different gender = -1 point
|
|
162
|
+
assert.equal(
|
|
163
|
+
certificationScore(
|
|
164
|
+
{
|
|
165
|
+
birthcountry: null,
|
|
166
|
+
birthdate: new Date("1946-08-17"),
|
|
167
|
+
birthplace: "75001",
|
|
168
|
+
family_name: "Bernard",
|
|
169
|
+
gender: "male",
|
|
170
|
+
given_name: "Stéphane",
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
birthcountry: null,
|
|
174
|
+
birthdate: new Date("1946-08-17"),
|
|
175
|
+
birthplace: "75001",
|
|
176
|
+
family_name: "Bernard",
|
|
177
|
+
gender: "female",
|
|
178
|
+
given_name: "Stéphane",
|
|
179
|
+
},
|
|
180
|
+
),
|
|
181
|
+
4,
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("matches first name with hyphen normalization", () => {
|
|
186
|
+
// Jean-Pierre and Jean Pierre should match (first name is "Jean")
|
|
187
|
+
assert.equal(
|
|
188
|
+
certificationScore(
|
|
189
|
+
{
|
|
190
|
+
birthcountry: null,
|
|
191
|
+
birthdate: new Date("1946-08-17"),
|
|
192
|
+
birthplace: "75001",
|
|
193
|
+
family_name: "Bernard",
|
|
194
|
+
gender: "male",
|
|
195
|
+
given_name: "Jean-Pierre",
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
birthcountry: null,
|
|
199
|
+
birthdate: new Date("1946-08-17"),
|
|
200
|
+
birthplace: "75001",
|
|
201
|
+
family_name: "Bernard",
|
|
202
|
+
gender: "male",
|
|
203
|
+
given_name: "Jean Marc",
|
|
204
|
+
},
|
|
205
|
+
),
|
|
206
|
+
5,
|
|
207
|
+
);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("birthplace with commune code conversion", () => {
|
|
211
|
+
// 75050 should convert to 92050
|
|
212
|
+
assert.equal(
|
|
213
|
+
certificationScore(
|
|
214
|
+
{
|
|
215
|
+
birthcountry: null,
|
|
216
|
+
birthdate: new Date("1946-08-17"),
|
|
217
|
+
birthplace: "75050",
|
|
218
|
+
family_name: "Bernard",
|
|
219
|
+
gender: "male",
|
|
220
|
+
given_name: "Stéphane",
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
birthcountry: null,
|
|
224
|
+
birthdate: new Date("1946-08-17"),
|
|
225
|
+
birthplace: "92050",
|
|
226
|
+
family_name: "Bernard",
|
|
227
|
+
gender: "male",
|
|
228
|
+
given_name: "Stéphane",
|
|
229
|
+
},
|
|
230
|
+
),
|
|
231
|
+
5,
|
|
232
|
+
);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("null birthplace in source still matches", () => {
|
|
236
|
+
// Null birthplace in source = still gets point
|
|
237
|
+
assert.equal(
|
|
238
|
+
certificationScore(
|
|
239
|
+
{
|
|
240
|
+
birthcountry: null,
|
|
241
|
+
birthdate: new Date("1946-08-17"),
|
|
242
|
+
birthplace: "75001",
|
|
243
|
+
family_name: "Bernard",
|
|
244
|
+
gender: "male",
|
|
245
|
+
given_name: "Stéphane",
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
birthcountry: null,
|
|
249
|
+
birthdate: new Date("1946-08-17"),
|
|
250
|
+
birthplace: null,
|
|
251
|
+
family_name: "Bernard",
|
|
252
|
+
gender: "male",
|
|
253
|
+
given_name: "Stéphane",
|
|
254
|
+
},
|
|
255
|
+
),
|
|
256
|
+
5,
|
|
257
|
+
);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it("foreign person with matching country", () => {
|
|
261
|
+
// For foreigners, check country code
|
|
262
|
+
assert.equal(
|
|
263
|
+
certificationScore(
|
|
264
|
+
{
|
|
265
|
+
birthcountry: "99136",
|
|
266
|
+
birthdate: new Date("1946-08-17"),
|
|
267
|
+
birthplace: null,
|
|
268
|
+
family_name: "O'Connor",
|
|
269
|
+
gender: "male",
|
|
270
|
+
given_name: "Patrick",
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
birthcountry: "99136",
|
|
274
|
+
birthdate: new Date("1946-08-17"),
|
|
275
|
+
birthplace: null,
|
|
276
|
+
family_name: "O'Connor",
|
|
277
|
+
gender: "male",
|
|
278
|
+
given_name: "Patrick",
|
|
279
|
+
},
|
|
280
|
+
),
|
|
281
|
+
5,
|
|
282
|
+
);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("foreign person with different country", () => {
|
|
286
|
+
// Different country = -1 point
|
|
287
|
+
assert.equal(
|
|
288
|
+
certificationScore(
|
|
289
|
+
{
|
|
290
|
+
birthcountry: "99136", // Ireland
|
|
291
|
+
birthdate: new Date("1946-08-17"),
|
|
292
|
+
birthplace: null,
|
|
293
|
+
family_name: "O'Connor",
|
|
294
|
+
gender: "male",
|
|
295
|
+
given_name: "Patrick",
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
birthcountry: "99132", // UK
|
|
299
|
+
birthdate: new Date("1946-08-17"),
|
|
300
|
+
birthplace: null,
|
|
301
|
+
family_name: "O'Connor",
|
|
302
|
+
gender: "male",
|
|
303
|
+
given_name: "Patrick",
|
|
304
|
+
},
|
|
305
|
+
),
|
|
306
|
+
4,
|
|
307
|
+
);
|
|
308
|
+
});
|
|
309
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
//
|
|
2
|
+
|
|
3
|
+
import type { IdentityVector } from "#src/types";
|
|
4
|
+
import { convertCommuneCode } from "./birthplace-conversion.js";
|
|
5
|
+
import { extractFirstName, normalizeText } from "./normalize.js";
|
|
6
|
+
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Calculates a certification score between identity from FranceConnect (IdentitePivot)
|
|
11
|
+
* and executive identity from business registries (SourceDirigeant).
|
|
12
|
+
*
|
|
13
|
+
* The scoring system awards 1 point for each matching criterion:
|
|
14
|
+
* - Family name (after normalization): 1 point
|
|
15
|
+
* - First name (first name only, after normalization): 1 point
|
|
16
|
+
* - Gender (match or absent in source): 1 point
|
|
17
|
+
* - Birth date (exact match): 1 point
|
|
18
|
+
* - Birth place:
|
|
19
|
+
* - For French (birthcountry = 99100 or null): commune match or absent in source: 1 point
|
|
20
|
+
* - For foreigners: country match: 1 point
|
|
21
|
+
*
|
|
22
|
+
* Maximum score: 5 points (required for certification)
|
|
23
|
+
*
|
|
24
|
+
* @param identitePivot - Identity from FranceConnect
|
|
25
|
+
* @param sourceDirigeant - Executive identity from business registry
|
|
26
|
+
* @returns Score from 0 to 5
|
|
27
|
+
*/
|
|
28
|
+
export function certificationScore(
|
|
29
|
+
identitePivot: IdentityVector,
|
|
30
|
+
sourceDirigeant: IdentityVector,
|
|
31
|
+
): number {
|
|
32
|
+
let score = 0;
|
|
33
|
+
|
|
34
|
+
// 1. Family name match (after normalization)
|
|
35
|
+
const normalizedFamilyNamePivot = normalizeText(identitePivot.family_name);
|
|
36
|
+
const normalizedFamilyNameSource = normalizeText(sourceDirigeant.family_name);
|
|
37
|
+
|
|
38
|
+
if (
|
|
39
|
+
normalizedFamilyNamePivot &&
|
|
40
|
+
normalizedFamilyNameSource &&
|
|
41
|
+
normalizedFamilyNamePivot === normalizedFamilyNameSource
|
|
42
|
+
) {
|
|
43
|
+
score += 1;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 2. First name match (first name only, after normalization)
|
|
47
|
+
const firstNamePivot = extractFirstName(identitePivot.given_name);
|
|
48
|
+
const firstNameSource = extractFirstName(sourceDirigeant.given_name);
|
|
49
|
+
|
|
50
|
+
if (firstNamePivot && firstNameSource && firstNamePivot === firstNameSource) {
|
|
51
|
+
score += 1;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 3. Gender match (match or absent in source)
|
|
55
|
+
if (
|
|
56
|
+
!sourceDirigeant.gender ||
|
|
57
|
+
identitePivot.gender === sourceDirigeant.gender
|
|
58
|
+
) {
|
|
59
|
+
score += 1;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 4. Birth date match (exact match)
|
|
63
|
+
if (
|
|
64
|
+
identitePivot.birthdate &&
|
|
65
|
+
sourceDirigeant.birthdate &&
|
|
66
|
+
identitePivot.birthdate.getTime() === sourceDirigeant.birthdate.getTime()
|
|
67
|
+
) {
|
|
68
|
+
score += 1;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 5. Birth place match (different logic for French vs foreigners)
|
|
72
|
+
const isFrench =
|
|
73
|
+
!identitePivot.birthcountry || identitePivot.birthcountry === "99100";
|
|
74
|
+
|
|
75
|
+
if (isFrench) {
|
|
76
|
+
// For French: check commune code (with conversion)
|
|
77
|
+
const communePivot = convertCommuneCode(identitePivot.birthplace);
|
|
78
|
+
const communeSource = convertCommuneCode(sourceDirigeant.birthplace);
|
|
79
|
+
|
|
80
|
+
// Match if equal or if source has no commune data
|
|
81
|
+
if (!communeSource || (communePivot && communePivot === communeSource)) {
|
|
82
|
+
score += 1;
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
// For foreigners: check country code (with conversion if needed from RNE)
|
|
86
|
+
// Both should already be in COG format (99XXX)
|
|
87
|
+
if (
|
|
88
|
+
identitePivot.birthcountry &&
|
|
89
|
+
sourceDirigeant.birthcountry &&
|
|
90
|
+
identitePivot.birthcountry === sourceDirigeant.birthcountry
|
|
91
|
+
) {
|
|
92
|
+
score += 1;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return score;
|
|
97
|
+
}
|
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
//
|
|
2
2
|
|
|
3
|
-
import { NotFoundError } from "#src/errors";
|
|
3
|
+
import { InvalidCertificationError, NotFoundError } from "#src/errors";
|
|
4
|
+
import { IdentityVectorSchema } from "#src/types";
|
|
4
5
|
import {
|
|
5
6
|
LiElJonsonFranceConnectUserInfo,
|
|
6
7
|
RogalDornFranceConnectUserInfo,
|
|
7
8
|
} from "#testing/seed/franceconnect";
|
|
9
|
+
import {
|
|
10
|
+
papillon_org_info,
|
|
11
|
+
rogal_dorn_org_info,
|
|
12
|
+
} from "#testing/seed/organizations";
|
|
13
|
+
import {
|
|
14
|
+
RogalDornMandataire,
|
|
15
|
+
UlysseToriMandataire,
|
|
16
|
+
} from "@proconnect-gouv/proconnect.api_entreprise/testing/seed/v3-infogreffe-rcs-unites_legales-siren-mandataires_sociaux";
|
|
8
17
|
import {
|
|
9
18
|
LiElJonsonEstablishment,
|
|
10
19
|
RogalDornEstablishment,
|
|
11
|
-
} from "
|
|
12
|
-
import { UlysseToriMandataire } from "#testing/seed/mandataires";
|
|
20
|
+
} from "@proconnect-gouv/proconnect.insee/testing/seed";
|
|
13
21
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
} from "@proconnect-gouv/proconnect.
|
|
22
|
+
RogalDornPouvoir,
|
|
23
|
+
UlysseTosiPouvoir,
|
|
24
|
+
} from "@proconnect-gouv/proconnect.registre_national_entreprises/testing/seed";
|
|
17
25
|
import assert from "node:assert/strict";
|
|
18
26
|
import { describe, it } from "node:test";
|
|
19
27
|
import { isOrganizationDirigeantFactory } from "./is-organization-dirigeant.js";
|
|
@@ -26,20 +34,33 @@ describe("isOrganizationDirigeantFactory", () => {
|
|
|
26
34
|
ApiEntrepriseInfogreffeRepository: {
|
|
27
35
|
findMandatairesSociauxBySiren: () => Promise.reject(new Error("💣")),
|
|
28
36
|
},
|
|
29
|
-
|
|
30
|
-
|
|
37
|
+
RegistreNationalEntreprisesApiRepository: {
|
|
38
|
+
findPouvoirsBySiren: () => Promise.reject(new Error("💣")),
|
|
31
39
|
},
|
|
32
40
|
InseeApiRepository: {
|
|
33
|
-
|
|
41
|
+
findBySiren: () => Promise.resolve(RogalDornEstablishment),
|
|
42
|
+
},
|
|
43
|
+
FranceConnectApiRepository: {
|
|
44
|
+
getFranceConnectUserInfo: () =>
|
|
45
|
+
Promise.resolve(RogalDornFranceConnectUserInfo),
|
|
34
46
|
},
|
|
35
|
-
getFranceConnectUserInfo: () =>
|
|
36
|
-
Promise.resolve(RogalDornFranceConnectUserInfo),
|
|
37
47
|
});
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
|
|
49
|
+
const isDirigeant = await isOrganizationDirigeant(rogal_dorn_org_info, 1);
|
|
50
|
+
|
|
51
|
+
assert.deepEqual(isDirigeant, {
|
|
52
|
+
cause: "exact_match",
|
|
53
|
+
details: {
|
|
54
|
+
dirigeant: {
|
|
55
|
+
...IdentityVectorSchema.parse(RogalDornFranceConnectUserInfo),
|
|
56
|
+
birthplace: null, // INSEE adapter returns null for foreigners
|
|
57
|
+
},
|
|
58
|
+
score: 5,
|
|
59
|
+
identity: IdentityVectorSchema.parse(RogalDornFranceConnectUserInfo),
|
|
60
|
+
source: "api.insee.fr/api-sirene/private",
|
|
61
|
+
},
|
|
62
|
+
ok: true,
|
|
63
|
+
});
|
|
43
64
|
});
|
|
44
65
|
|
|
45
66
|
it("should not match another mandataire", async () => {
|
|
@@ -47,39 +68,105 @@ describe("isOrganizationDirigeantFactory", () => {
|
|
|
47
68
|
ApiEntrepriseInfogreffeRepository: {
|
|
48
69
|
findMandatairesSociauxBySiren: () => Promise.reject(new Error("💣")),
|
|
49
70
|
},
|
|
50
|
-
|
|
51
|
-
|
|
71
|
+
RegistreNationalEntreprisesApiRepository: {
|
|
72
|
+
findPouvoirsBySiren: () => Promise.reject(new Error("💣")),
|
|
52
73
|
},
|
|
53
74
|
InseeApiRepository: {
|
|
54
|
-
|
|
75
|
+
findBySiren: () => Promise.resolve(LiElJonsonEstablishment),
|
|
76
|
+
},
|
|
77
|
+
FranceConnectApiRepository: {
|
|
78
|
+
getFranceConnectUserInfo: () =>
|
|
79
|
+
Promise.resolve(RogalDornFranceConnectUserInfo),
|
|
55
80
|
},
|
|
56
|
-
getFranceConnectUserInfo: () =>
|
|
57
|
-
Promise.resolve(RogalDornFranceConnectUserInfo),
|
|
58
81
|
});
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
82
|
+
|
|
83
|
+
const isDirigeant = await isOrganizationDirigeant(rogal_dorn_org_info, 1);
|
|
84
|
+
|
|
85
|
+
assert.deepEqual(isDirigeant, {
|
|
86
|
+
cause: "below_threshold",
|
|
87
|
+
details: {
|
|
88
|
+
dirigeant: {
|
|
89
|
+
...IdentityVectorSchema.parse(LiElJonsonFranceConnectUserInfo),
|
|
90
|
+
birthcountry: null, // INSEE adapter returns null for French people
|
|
91
|
+
},
|
|
92
|
+
score: 1,
|
|
93
|
+
identity: IdentityVectorSchema.parse(RogalDornFranceConnectUserInfo),
|
|
94
|
+
source: "api.insee.fr/api-sirene/private",
|
|
95
|
+
},
|
|
96
|
+
ok: false,
|
|
97
|
+
});
|
|
64
98
|
});
|
|
65
99
|
|
|
66
|
-
it("should match
|
|
100
|
+
it("should match Rogal Dorn among the executive of Papillon in RNE", async () => {
|
|
101
|
+
const isOrganizationDirigeant = isOrganizationDirigeantFactory({
|
|
102
|
+
ApiEntrepriseInfogreffeRepository: {
|
|
103
|
+
findMandatairesSociauxBySiren: () => Promise.reject(new Error("💣")),
|
|
104
|
+
},
|
|
105
|
+
RegistreNationalEntreprisesApiRepository: {
|
|
106
|
+
findPouvoirsBySiren: () =>
|
|
107
|
+
Promise.resolve([UlysseTosiPouvoir, RogalDornPouvoir]),
|
|
108
|
+
},
|
|
109
|
+
InseeApiRepository: {
|
|
110
|
+
findBySiren: () => Promise.reject(new Error("💣")),
|
|
111
|
+
},
|
|
112
|
+
FranceConnectApiRepository: {
|
|
113
|
+
getFranceConnectUserInfo: () =>
|
|
114
|
+
Promise.resolve(RogalDornFranceConnectUserInfo),
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const isDirigeant = await isOrganizationDirigeant(papillon_org_info, 1);
|
|
119
|
+
|
|
120
|
+
assert.deepEqual(isDirigeant, {
|
|
121
|
+
cause: "exact_match",
|
|
122
|
+
details: {
|
|
123
|
+
dirigeant: {
|
|
124
|
+
...IdentityVectorSchema.parse(RogalDornFranceConnectUserInfo),
|
|
125
|
+
birthplace: null, // RNE adapter returns null for foreigners
|
|
126
|
+
},
|
|
127
|
+
score: 5,
|
|
128
|
+
identity: IdentityVectorSchema.parse(RogalDornFranceConnectUserInfo),
|
|
129
|
+
source: "registre-national-entreprises.inpi.fr/api",
|
|
130
|
+
},
|
|
131
|
+
ok: true,
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("should match Rogal Dorn among the executive of Papillon in Infogreffe", async () => {
|
|
67
136
|
const isOrganizationDirigeant = isOrganizationDirigeantFactory({
|
|
68
137
|
ApiEntrepriseInfogreffeRepository: {
|
|
69
138
|
findMandatairesSociauxBySiren: () =>
|
|
70
|
-
Promise.resolve([UlysseToriMandataire]),
|
|
139
|
+
Promise.resolve([UlysseToriMandataire, RogalDornMandataire]),
|
|
71
140
|
},
|
|
72
|
-
|
|
73
|
-
|
|
141
|
+
RegistreNationalEntreprisesApiRepository: {
|
|
142
|
+
findPouvoirsBySiren: () => Promise.reject(new Error("💣")),
|
|
74
143
|
},
|
|
75
144
|
InseeApiRepository: {
|
|
76
|
-
|
|
145
|
+
findBySiren: () => Promise.reject(new Error("💣")),
|
|
146
|
+
},
|
|
147
|
+
FranceConnectApiRepository: {
|
|
148
|
+
getFranceConnectUserInfo: () =>
|
|
149
|
+
Promise.resolve(RogalDornFranceConnectUserInfo),
|
|
77
150
|
},
|
|
78
|
-
getFranceConnectUserInfo: () =>
|
|
79
|
-
Promise.resolve(RogalDornFranceConnectUserInfo),
|
|
80
151
|
});
|
|
81
|
-
|
|
82
|
-
|
|
152
|
+
|
|
153
|
+
const isDirigeant = await isOrganizationDirigeant(papillon_org_info, 1);
|
|
154
|
+
|
|
155
|
+
assert.deepEqual(isDirigeant, {
|
|
156
|
+
cause: "exact_match",
|
|
157
|
+
details: {
|
|
158
|
+
dirigeant: {
|
|
159
|
+
...IdentityVectorSchema.parse(RogalDornFranceConnectUserInfo),
|
|
160
|
+
birthplace: null, // API Entreprise adapter doesn't provide birthplace
|
|
161
|
+
gender: null, // API Entreprise adapter doesn't provide gender
|
|
162
|
+
},
|
|
163
|
+
score: 5,
|
|
164
|
+
identity: IdentityVectorSchema.parse(RogalDornFranceConnectUserInfo),
|
|
165
|
+
source:
|
|
166
|
+
"entreprise.api.gouv.fr/v3/infogreffe/rcs/unites_legales/{siren}/mandataires_sociaux",
|
|
167
|
+
},
|
|
168
|
+
ok: true,
|
|
169
|
+
});
|
|
83
170
|
});
|
|
84
171
|
|
|
85
172
|
it("❎ fail with no franceconnect user info", async () => {
|
|
@@ -87,17 +174,19 @@ describe("isOrganizationDirigeantFactory", () => {
|
|
|
87
174
|
ApiEntrepriseInfogreffeRepository: {
|
|
88
175
|
findMandatairesSociauxBySiren: () => Promise.reject(new Error("💣")),
|
|
89
176
|
},
|
|
90
|
-
|
|
91
|
-
|
|
177
|
+
RegistreNationalEntreprisesApiRepository: {
|
|
178
|
+
findPouvoirsBySiren: () => Promise.reject(new Error("💣")),
|
|
92
179
|
},
|
|
93
180
|
InseeApiRepository: {
|
|
94
|
-
|
|
181
|
+
findBySiren: () => Promise.reject(new Error("💣")),
|
|
182
|
+
},
|
|
183
|
+
FranceConnectApiRepository: {
|
|
184
|
+
getFranceConnectUserInfo: () => Promise.resolve(undefined),
|
|
95
185
|
},
|
|
96
|
-
getFranceConnectUserInfo: () => Promise.resolve(undefined),
|
|
97
186
|
});
|
|
98
187
|
|
|
99
188
|
await assert.rejects(
|
|
100
|
-
isOrganizationDirigeant(
|
|
189
|
+
isOrganizationDirigeant(rogal_dorn_org_info, 1),
|
|
101
190
|
new NotFoundError("FranceConnect UserInfo not found"),
|
|
102
191
|
);
|
|
103
192
|
});
|
|
@@ -105,21 +194,23 @@ describe("isOrganizationDirigeantFactory", () => {
|
|
|
105
194
|
it("❎ fail with no mandataires", async () => {
|
|
106
195
|
const isOrganizationDirigeant = isOrganizationDirigeantFactory({
|
|
107
196
|
ApiEntrepriseInfogreffeRepository: {
|
|
108
|
-
findMandatairesSociauxBySiren: () => Promise.
|
|
197
|
+
findMandatairesSociauxBySiren: () => Promise.reject(new Error("💣")),
|
|
109
198
|
},
|
|
110
|
-
|
|
111
|
-
|
|
199
|
+
RegistreNationalEntreprisesApiRepository: {
|
|
200
|
+
findPouvoirsBySiren: () => Promise.resolve([]),
|
|
112
201
|
},
|
|
113
202
|
InseeApiRepository: {
|
|
114
|
-
|
|
203
|
+
findBySiren: () => Promise.reject(new Error("💣")),
|
|
204
|
+
},
|
|
205
|
+
FranceConnectApiRepository: {
|
|
206
|
+
getFranceConnectUserInfo: () =>
|
|
207
|
+
Promise.resolve(LiElJonsonFranceConnectUserInfo),
|
|
115
208
|
},
|
|
116
|
-
getFranceConnectUserInfo: () =>
|
|
117
|
-
Promise.resolve(LiElJonsonFranceConnectUserInfo),
|
|
118
209
|
});
|
|
119
210
|
|
|
120
211
|
await assert.rejects(
|
|
121
|
-
isOrganizationDirigeant(
|
|
122
|
-
new
|
|
212
|
+
isOrganizationDirigeant(papillon_org_info, 1),
|
|
213
|
+
new InvalidCertificationError("No candidates found"),
|
|
123
214
|
);
|
|
124
215
|
});
|
|
125
216
|
});
|