@mikashboks/capture-intelligence-core 0.2.4 → 0.2.6

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 (49) hide show
  1. package/dist/cjs/capture-envelope.d.ts +79 -0
  2. package/dist/cjs/capture-envelope.d.ts.map +1 -0
  3. package/dist/cjs/capture-envelope.js +48 -0
  4. package/dist/cjs/capture-envelope.js.map +1 -0
  5. package/dist/cjs/capture-guidance.js +3 -3
  6. package/dist/cjs/capture-guidance.js.map +1 -1
  7. package/dist/cjs/capture-trust.d.ts +1 -1
  8. package/dist/cjs/capture-trust.d.ts.map +1 -1
  9. package/dist/cjs/capture-trust.js +11 -0
  10. package/dist/cjs/capture-trust.js.map +1 -1
  11. package/dist/cjs/document-crop-arbitration.d.ts +117 -0
  12. package/dist/cjs/document-crop-arbitration.d.ts.map +1 -0
  13. package/dist/cjs/document-crop-arbitration.js +673 -0
  14. package/dist/cjs/document-crop-arbitration.js.map +1 -0
  15. package/dist/cjs/document-profiles.d.ts +23 -0
  16. package/dist/cjs/document-profiles.d.ts.map +1 -0
  17. package/dist/cjs/document-profiles.js +269 -0
  18. package/dist/cjs/document-profiles.js.map +1 -0
  19. package/dist/cjs/index.d.ts +6 -0
  20. package/dist/cjs/index.d.ts.map +1 -1
  21. package/dist/cjs/index.js +46 -2
  22. package/dist/cjs/index.js.map +1 -1
  23. package/dist/cjs/version.d.ts +1 -1
  24. package/dist/cjs/version.js +1 -1
  25. package/dist/esm/capture-envelope.d.ts +79 -0
  26. package/dist/esm/capture-envelope.d.ts.map +1 -0
  27. package/dist/esm/capture-envelope.js +43 -0
  28. package/dist/esm/capture-envelope.js.map +1 -0
  29. package/dist/esm/capture-guidance.js +3 -3
  30. package/dist/esm/capture-guidance.js.map +1 -1
  31. package/dist/esm/capture-trust.d.ts +1 -1
  32. package/dist/esm/capture-trust.d.ts.map +1 -1
  33. package/dist/esm/capture-trust.js +11 -0
  34. package/dist/esm/capture-trust.js.map +1 -1
  35. package/dist/esm/document-crop-arbitration.d.ts +117 -0
  36. package/dist/esm/document-crop-arbitration.d.ts.map +1 -0
  37. package/dist/esm/document-crop-arbitration.js +644 -0
  38. package/dist/esm/document-crop-arbitration.js.map +1 -0
  39. package/dist/esm/document-profiles.d.ts +23 -0
  40. package/dist/esm/document-profiles.d.ts.map +1 -0
  41. package/dist/esm/document-profiles.js +265 -0
  42. package/dist/esm/document-profiles.js.map +1 -0
  43. package/dist/esm/index.d.ts +6 -0
  44. package/dist/esm/index.d.ts.map +1 -1
  45. package/dist/esm/index.js +3 -0
  46. package/dist/esm/index.js.map +1 -1
  47. package/dist/esm/version.d.ts +1 -1
  48. package/dist/esm/version.js +1 -1
  49. package/package.json +1 -1
@@ -0,0 +1,673 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getDocumentCropRejectedReason = exports.isDocumentCropVerified = exports.canTrustReadySafeCardEvidenceOverNativeUnsafeRisk = exports.hasNativeClampedCaptureRisk = exports.hasNativeOverfullCaptureRisk = exports.hasHardCaptureCutOffEvidence = exports.hasReadySafeCardLiveEvidence = exports.hasReadyCaptureCandidateDecision = exports.hasTrustedCardFrontRecognitionReason = exports.hasTrustedMrzCaptureEvidence = exports.getCapturePageRecognitionText = exports.hasUnsafeInsetReliableCaptureQuad = exports.hasMarginSafeReliableCaptureQuad = exports.hasMarginSafeDocumentCropEvidence = exports.hasCaptureDocumentCutOffEvidence = exports.hasExplicitUnsafeCaptureReason = exports.hasExplicitDocumentCutOffReason = exports.getMeasuredDocumentCropMinAreaRatio = exports.isDocumentCropAspectSafe = exports.getDocumentCropSafetyAspectRatio = exports.getDocumentCropSafetyThresholds = exports.getDocumentCropShapeFamily = exports.isCardLikeCropAspect = exports.SAFE_LIVE_QUAD_INSET_MIN = exports.QUIET_ZONE_CLAMP_CUT_OFF_RISK_LOSS = exports.CARD_LIKE_MEASURED_CROP_MIN_AREA_RATIO = exports.DOCUMENT_CROP_ASPECT_MAX = exports.DOCUMENT_CROP_ASPECT_MIN = exports.DEFAULT_SUBMIT_CROP_INSET_FLOOR_ENABLED = exports.DEFAULT_RECTANGLE_CROP_CONFIDENCE_THRESHOLD = exports.DEFAULT_CROP_CONFIDENCE_THRESHOLD = void 0;
4
+ exports.assessDocumentCropCandidate = assessDocumentCropCandidate;
5
+ exports.getDocumentCropReviewPolicy = getDocumentCropReviewPolicy;
6
+ exports.getDocumentCropTrustTier = getDocumentCropTrustTier;
7
+ const capture_trust_js_1 = require("./capture-trust.js");
8
+ exports.DEFAULT_CROP_CONFIDENCE_THRESHOLD = 0.8;
9
+ exports.DEFAULT_RECTANGLE_CROP_CONFIDENCE_THRESHOLD = 0.88;
10
+ exports.DEFAULT_SUBMIT_CROP_INSET_FLOOR_ENABLED = true;
11
+ exports.DOCUMENT_CROP_ASPECT_MIN = 0.45;
12
+ exports.DOCUMENT_CROP_ASPECT_MAX = 2.15;
13
+ exports.CARD_LIKE_MEASURED_CROP_MIN_AREA_RATIO = 0.06;
14
+ exports.QUIET_ZONE_CLAMP_CUT_OFF_RISK_LOSS = 0.8;
15
+ exports.SAFE_LIVE_QUAD_INSET_MIN = 0.02;
16
+ const DOCUMENT_CROP_VISIBLE_EDGE_INSET_RATIO = 0.02;
17
+ const REVIEW_AUTO_CROP_VISIBLE_EDGE_INSET_RATIO = 0.05;
18
+ const DEFAULT_SUBMIT_CROP_VISIBLE_EDGE_INSET_RATIO = 0.055;
19
+ const TRUSTED_LIVE_BOUNDS_MIN_AREA_RATIO = 0.035;
20
+ const DEFAULT_CROP_OUTPUT_MIN_LONG_SIDE = 900;
21
+ const DEFAULT_CROP_OUTPUT_MIN_SHORT_SIDE = 500;
22
+ const TRUSTED_LIVE_BOUNDS_CROP_OUTPUT_MIN_LONG_SIDE = 520;
23
+ const TRUSTED_LIVE_BOUNDS_CROP_OUTPUT_MIN_SHORT_SIDE = 300;
24
+ const CAPTURE_CANDIDATE_HIGH_CLASSIFIER_CONFIDENCE = 70;
25
+ const CAPTURE_CANDIDATE_MRZ_CONFIRMED_CONFIDENCE_MIN = 0.45;
26
+ const NATIVE_OVERFULL_AREA_RATIO_MIN = 0.92;
27
+ const EXPLICIT_DOCUMENT_CUT_OFF_REASONS = new Set([
28
+ 'cut_off',
29
+ 'cutoff',
30
+ 'document_cut_off',
31
+ 'document_cutoff',
32
+ 'edge_cut_off',
33
+ 'missing_edge',
34
+ 'missing_edges',
35
+ 'partially_cropped',
36
+ 'border_touching',
37
+ 'border_touched',
38
+ 'touching_border',
39
+ ]);
40
+ const EXPLICIT_UNSAFE_CAPTURE_REASONS = new Set([
41
+ ...EXPLICIT_DOCUMENT_CUT_OFF_REASONS,
42
+ 'unsafe_crop',
43
+ 'unsafe_crop_rejected',
44
+ 'crop_too_narrow',
45
+ 'too_narrow',
46
+ ]);
47
+ const DOCUMENT_CROP_SAFETY_THRESHOLDS_BY_FAMILY = {
48
+ 'card-like': {
49
+ minAreaRatio: 0.2,
50
+ minWidthRatio: 0.18,
51
+ minHeightRatio: 0.12,
52
+ maxHorizontalOutsideMarginRatio: 0.62,
53
+ maxVerticalOutsideMarginRatio: 0.62,
54
+ aspectMin: 1.1,
55
+ aspectMax: exports.DOCUMENT_CROP_ASPECT_MAX,
56
+ },
57
+ 'passport-like': {
58
+ minAreaRatio: 0.1,
59
+ minWidthRatio: 0.18,
60
+ minHeightRatio: 0.18,
61
+ maxHorizontalOutsideMarginRatio: 0.55,
62
+ maxVerticalOutsideMarginRatio: 0.55,
63
+ aspectMin: 1.05,
64
+ aspectMax: 1.85,
65
+ useNormalizedAspect: true,
66
+ },
67
+ 'document-page-like': {
68
+ minAreaRatio: 0.14,
69
+ minWidthRatio: 0.22,
70
+ minHeightRatio: 0.18,
71
+ maxHorizontalOutsideMarginRatio: 0.48,
72
+ maxVerticalOutsideMarginRatio: 0.48,
73
+ aspectMin: 0.6,
74
+ aspectMax: 1.65,
75
+ useNormalizedAspect: true,
76
+ },
77
+ };
78
+ const isFiniteNumber = (value) => typeof value === 'number' && Number.isFinite(value);
79
+ const normalizeReason = (reason) => (reason ?? '').trim().toLowerCase();
80
+ const isCardLikeCropAspect = (aspectRatio) => Number.isFinite(aspectRatio) && aspectRatio >= 1.1 && aspectRatio <= exports.DOCUMENT_CROP_ASPECT_MAX;
81
+ exports.isCardLikeCropAspect = isCardLikeCropAspect;
82
+ const getDocumentCropShapeFamily = (aspectRatio) => {
83
+ if ((0, exports.isCardLikeCropAspect)(aspectRatio))
84
+ return 'card-like';
85
+ if (Number.isFinite(aspectRatio) && aspectRatio >= 0.6 && aspectRatio <= 1.85) {
86
+ return 'passport-like';
87
+ }
88
+ return 'document-page-like';
89
+ };
90
+ exports.getDocumentCropShapeFamily = getDocumentCropShapeFamily;
91
+ const getDocumentCropSafetyThresholds = (aspectRatio, shapeFamily) => {
92
+ return DOCUMENT_CROP_SAFETY_THRESHOLDS_BY_FAMILY[shapeFamily ?? (0, exports.getDocumentCropShapeFamily)(aspectRatio)];
93
+ };
94
+ exports.getDocumentCropSafetyThresholds = getDocumentCropSafetyThresholds;
95
+ const getDocumentCropSafetyAspectRatio = (aspectRatio, safetyThresholds) => safetyThresholds.useNormalizedAspect && Number.isFinite(aspectRatio) && aspectRatio > 0
96
+ ? Math.max(aspectRatio, 1 / aspectRatio)
97
+ : aspectRatio;
98
+ exports.getDocumentCropSafetyAspectRatio = getDocumentCropSafetyAspectRatio;
99
+ const isDocumentCropAspectSafe = (aspectRatio, safetyThresholds) => {
100
+ const effectiveAspectRatio = (0, exports.getDocumentCropSafetyAspectRatio)(aspectRatio, safetyThresholds);
101
+ return (Number.isFinite(effectiveAspectRatio) &&
102
+ effectiveAspectRatio >= safetyThresholds.aspectMin &&
103
+ effectiveAspectRatio <= safetyThresholds.aspectMax);
104
+ };
105
+ exports.isDocumentCropAspectSafe = isDocumentCropAspectSafe;
106
+ const getMeasuredDocumentCropMinAreaRatio = (aspectRatio, shapeFamily) => {
107
+ const resolvedShapeFamily = shapeFamily ?? (0, exports.getDocumentCropShapeFamily)(aspectRatio);
108
+ const safetyThresholds = (0, exports.getDocumentCropSafetyThresholds)(aspectRatio, resolvedShapeFamily);
109
+ if (resolvedShapeFamily === 'card-like' &&
110
+ (0, exports.isDocumentCropAspectSafe)(aspectRatio, safetyThresholds)) {
111
+ return Math.min(safetyThresholds.minAreaRatio, exports.CARD_LIKE_MEASURED_CROP_MIN_AREA_RATIO);
112
+ }
113
+ return safetyThresholds.minAreaRatio;
114
+ };
115
+ exports.getMeasuredDocumentCropMinAreaRatio = getMeasuredDocumentCropMinAreaRatio;
116
+ const hasExplicitDocumentCutOffReason = (reason) => EXPLICIT_DOCUMENT_CUT_OFF_REASONS.has(normalizeReason(reason));
117
+ exports.hasExplicitDocumentCutOffReason = hasExplicitDocumentCutOffReason;
118
+ const hasExplicitUnsafeCaptureReason = (reason) => EXPLICIT_UNSAFE_CAPTURE_REASONS.has(normalizeReason(reason));
119
+ exports.hasExplicitUnsafeCaptureReason = hasExplicitUnsafeCaptureReason;
120
+ const hasCaptureDocumentCutOffEvidence = (metadata) => {
121
+ if (!metadata)
122
+ return false;
123
+ return ((0, exports.hasExplicitDocumentCutOffReason)(metadata.reason) ||
124
+ (0, exports.hasExplicitUnsafeCaptureReason)(metadata.reason));
125
+ };
126
+ exports.hasCaptureDocumentCutOffEvidence = hasCaptureDocumentCutOffEvidence;
127
+ const isContourBackedDocumentBoundsDetector = (detector) => detector === 'scanic' || detector === 'kt-jscanify';
128
+ const isNativeViewfinderCrop = (metadata) => Boolean(metadata &&
129
+ (metadata.cropSource === 'viewfinder_candidate' ||
130
+ metadata.scannerEngine === 'native-viewfinder' ||
131
+ metadata.reason === 'web_native_viewfinder_capture'));
132
+ const isMeasuredCardViewfinderCrop = (metadata) => metadata.scannerEngine === 'native-viewfinder' &&
133
+ metadata.cropSource === 'viewfinder_candidate' &&
134
+ metadata.cropShapeFamily === 'card-like';
135
+ const isTrustedLiveBoundsCrop = (metadata) => metadata.cropSource === 'live_quad_candidate' &&
136
+ metadata.cropVerified === true &&
137
+ metadata.cornersOk === true &&
138
+ metadata.liveBoundsReliable !== false &&
139
+ isFiniteNumber(metadata.liveQuadInsetMin) &&
140
+ metadata.liveQuadInsetMin >= exports.SAFE_LIVE_QUAD_INSET_MIN &&
141
+ (metadata.cropShapeFamily === 'card-like' || metadata.cropShapeFamily === 'passport-like');
142
+ const isMrzAnchoredPassportDataPageCrop = (metadata) => metadata.cropSource === 'mrz_anchor_candidate' &&
143
+ metadata.reason === 'detected_passport_data_page' &&
144
+ metadata.cornersOk === true &&
145
+ metadata.cropShapeFamily === 'passport-like';
146
+ const hasUnsafeLiveQuadCropEvidence = (metadata) => Boolean(metadata.cropSource === 'live_quad_candidate' &&
147
+ metadata.liveQuadPresent !== false &&
148
+ isFiniteNumber(metadata.liveQuadInsetMin) &&
149
+ metadata.liveQuadInsetMin < exports.SAFE_LIVE_QUAD_INSET_MIN);
150
+ const isReviewAutoCrop = (metadata) => metadata.cropProvenance === 'review_auto_crop' || metadata.cropSource === 'review_auto_crop';
151
+ const getDefaultableVisibleEdgeInsetRatio = (metadata) => isReviewAutoCrop(metadata)
152
+ ? REVIEW_AUTO_CROP_VISIBLE_EDGE_INSET_RATIO
153
+ : DOCUMENT_CROP_VISIBLE_EDGE_INSET_RATIO;
154
+ const hasQuietZoneClampCutOffEvidence = (metadata) => Boolean(metadata &&
155
+ isFiniteNumber(metadata.quietZoneClampLossFraction) &&
156
+ metadata.quietZoneClampLossFraction >= exports.QUIET_ZONE_CLAMP_CUT_OFF_RISK_LOSS);
157
+ const getCropAreaRatio = (rect, sourceImageSize) => (rect.w * rect.h) / Math.max(1, sourceImageSize.width * sourceImageSize.height);
158
+ const getCropAspectRatio = (rect) => (rect.h > 0 ? rect.w / rect.h : 0);
159
+ const getCropMinAreaRatio = (metadata, aspectRatio, shapeFamily) => {
160
+ if (isTrustedLiveBoundsCrop(metadata)) {
161
+ return Math.min((0, exports.getDocumentCropSafetyThresholds)(aspectRatio, shapeFamily).minAreaRatio, TRUSTED_LIVE_BOUNDS_MIN_AREA_RATIO);
162
+ }
163
+ if (isMeasuredCardViewfinderCrop(metadata)) {
164
+ return (0, exports.getMeasuredDocumentCropMinAreaRatio)(aspectRatio, shapeFamily);
165
+ }
166
+ return (0, exports.getDocumentCropSafetyThresholds)(aspectRatio, shapeFamily).minAreaRatio;
167
+ };
168
+ const hasDocumentCropGeometry = (metadata) => Boolean(metadata.rect && metadata.sourceImageSize);
169
+ const getCropSafetyInputs = (metadata) => {
170
+ const { rect, sourceImageSize } = metadata;
171
+ if (!rect || !sourceImageSize)
172
+ return null;
173
+ if (!isFiniteNumber(sourceImageSize.width) ||
174
+ !isFiniteNumber(sourceImageSize.height) ||
175
+ sourceImageSize.width <= 0 ||
176
+ sourceImageSize.height <= 0 ||
177
+ !isFiniteNumber(rect.x) ||
178
+ !isFiniteNumber(rect.y) ||
179
+ !isFiniteNumber(rect.w) ||
180
+ !isFiniteNumber(rect.h) ||
181
+ rect.w <= 0 ||
182
+ rect.h <= 0) {
183
+ return null;
184
+ }
185
+ const width = Math.max(1, sourceImageSize.width);
186
+ const height = Math.max(1, sourceImageSize.height);
187
+ const widthRatio = rect.w / width;
188
+ const heightRatio = rect.h / height;
189
+ const leftMarginRatio = rect.x / width;
190
+ const topMarginRatio = rect.y / height;
191
+ const rightMarginRatio = (sourceImageSize.width - (rect.x + rect.w)) / width;
192
+ const bottomMarginRatio = (sourceImageSize.height - (rect.y + rect.h)) / height;
193
+ const areaRatio = isFiniteNumber(metadata.areaRatio)
194
+ ? metadata.areaRatio
195
+ : getCropAreaRatio(rect, sourceImageSize);
196
+ const aspectRatio = isFiniteNumber(metadata.aspectRatio)
197
+ ? metadata.aspectRatio
198
+ : getCropAspectRatio(rect);
199
+ return {
200
+ rect,
201
+ sourceImageSize,
202
+ widthRatio,
203
+ heightRatio,
204
+ leftMarginRatio,
205
+ topMarginRatio,
206
+ rightMarginRatio,
207
+ bottomMarginRatio,
208
+ areaRatio,
209
+ aspectRatio,
210
+ shapeFamily: metadata.cropShapeFamily,
211
+ };
212
+ };
213
+ const isDocumentCropGeometrySafe = (metadata) => {
214
+ if (metadata.clamped || metadata.unsafeReason)
215
+ return false;
216
+ const safetyInputs = getCropSafetyInputs(metadata);
217
+ if (!safetyInputs)
218
+ return false;
219
+ const { widthRatio, heightRatio, leftMarginRatio, topMarginRatio, rightMarginRatio, bottomMarginRatio, areaRatio, aspectRatio, shapeFamily, } = safetyInputs;
220
+ const safetyThresholds = (0, exports.getDocumentCropSafetyThresholds)(aspectRatio, shapeFamily);
221
+ const minAreaRatio = getCropMinAreaRatio(metadata, aspectRatio, shapeFamily);
222
+ if (!isFiniteNumber(areaRatio) || !isFiniteNumber(aspectRatio))
223
+ return false;
224
+ if (areaRatio < minAreaRatio)
225
+ return false;
226
+ if (widthRatio < safetyThresholds.minWidthRatio ||
227
+ heightRatio < safetyThresholds.minHeightRatio) {
228
+ return false;
229
+ }
230
+ if (!(0, exports.isDocumentCropAspectSafe)(aspectRatio, safetyThresholds))
231
+ return false;
232
+ if (leftMarginRatio < 0 ||
233
+ rightMarginRatio < 0 ||
234
+ topMarginRatio < 0 ||
235
+ bottomMarginRatio < 0 ||
236
+ leftMarginRatio > safetyThresholds.maxHorizontalOutsideMarginRatio ||
237
+ rightMarginRatio > safetyThresholds.maxHorizontalOutsideMarginRatio ||
238
+ topMarginRatio > safetyThresholds.maxVerticalOutsideMarginRatio ||
239
+ bottomMarginRatio > safetyThresholds.maxVerticalOutsideMarginRatio) {
240
+ return false;
241
+ }
242
+ return true;
243
+ };
244
+ const getPointInsetRatio = (point, sourceImageSize) => Math.min(point.x / Math.max(1, sourceImageSize.width), point.y / Math.max(1, sourceImageSize.height), (sourceImageSize.width - point.x) / Math.max(1, sourceImageSize.width), (sourceImageSize.height - point.y) / Math.max(1, sourceImageSize.height));
245
+ const getMinimumPolygonInsetRatio = (metadata) => {
246
+ const { polygon, sourceImageSize } = metadata;
247
+ if (!polygon || !sourceImageSize)
248
+ return null;
249
+ const points = [polygon.topLeft, polygon.topRight, polygon.bottomRight, polygon.bottomLeft];
250
+ if (points.some(point => !isFiniteNumber(point.x) || !isFiniteNumber(point.y)) ||
251
+ !isFiniteNumber(sourceImageSize.width) ||
252
+ !isFiniteNumber(sourceImageSize.height) ||
253
+ sourceImageSize.width <= 0 ||
254
+ sourceImageSize.height <= 0) {
255
+ return null;
256
+ }
257
+ return Math.min(...points.map(point => getPointInsetRatio(point, sourceImageSize)));
258
+ };
259
+ const getMinimumCropInsetRatio = (metadata) => {
260
+ const polygonInsetRatio = getMinimumPolygonInsetRatio(metadata);
261
+ if (polygonInsetRatio !== null)
262
+ return polygonInsetRatio;
263
+ if (!isReviewAutoCrop(metadata))
264
+ return null;
265
+ const safetyInputs = getCropSafetyInputs(metadata);
266
+ if (!safetyInputs)
267
+ return null;
268
+ return Math.min(safetyInputs.leftMarginRatio, safetyInputs.topMarginRatio, safetyInputs.rightMarginRatio, safetyInputs.bottomMarginRatio);
269
+ };
270
+ const getMinimumCropBoundsInsetRatio = (metadata) => {
271
+ const polygonInsetRatio = getMinimumPolygonInsetRatio(metadata);
272
+ if (polygonInsetRatio !== null)
273
+ return polygonInsetRatio;
274
+ const safetyInputs = getCropSafetyInputs(metadata);
275
+ if (!safetyInputs)
276
+ return null;
277
+ return Math.min(safetyInputs.leftMarginRatio, safetyInputs.topMarginRatio, safetyInputs.rightMarginRatio, safetyInputs.bottomMarginRatio);
278
+ };
279
+ const hasMarginSafeDocumentCropEvidence = (metadata) => {
280
+ const minimumInsetRatio = getMinimumCropInsetRatio(metadata);
281
+ const minimumRequiredInsetRatio = getDefaultableVisibleEdgeInsetRatio(metadata);
282
+ return Boolean((metadata.confidence ?? 0) >= 0.6 &&
283
+ minimumInsetRatio !== null &&
284
+ minimumInsetRatio >= minimumRequiredInsetRatio &&
285
+ isDocumentCropGeometrySafe(metadata));
286
+ };
287
+ exports.hasMarginSafeDocumentCropEvidence = hasMarginSafeDocumentCropEvidence;
288
+ const hasMarginSafeReliableCaptureQuad = (metadata) => Boolean(metadata?.liveQuadPresent &&
289
+ isContourBackedDocumentBoundsDetector(metadata.liveBoundsDetector) &&
290
+ metadata.liveBoundsReliable !== false &&
291
+ isFiniteNumber(metadata.liveQuadInsetMin) &&
292
+ metadata.liveQuadInsetMin >= exports.SAFE_LIVE_QUAD_INSET_MIN);
293
+ exports.hasMarginSafeReliableCaptureQuad = hasMarginSafeReliableCaptureQuad;
294
+ const hasUnsafeInsetReliableCaptureQuad = (metadata) => Boolean(metadata?.liveQuadPresent &&
295
+ isContourBackedDocumentBoundsDetector(metadata.liveBoundsDetector) &&
296
+ metadata.liveBoundsReliable !== false &&
297
+ isFiniteNumber(metadata.liveQuadInsetMin) &&
298
+ metadata.liveQuadInsetMin < exports.SAFE_LIVE_QUAD_INSET_MIN);
299
+ exports.hasUnsafeInsetReliableCaptureQuad = hasUnsafeInsetReliableCaptureQuad;
300
+ const normalizeEvidenceKey = (value) => String(value || '')
301
+ .trim()
302
+ .toLowerCase()
303
+ .replace(/[\s-]+/g, '_');
304
+ const isBackSideStrategy = (strategy) => normalizeEvidenceKey(strategy).endsWith('_back');
305
+ const isCardFrontCaptureContext = (metadata) => {
306
+ if (metadata?.cropShapeFamily !== 'card-like')
307
+ return false;
308
+ if (isBackSideStrategy(metadata?.liveRecognitionStrategy))
309
+ return false;
310
+ if (metadata?.liveRecognitionStrategy)
311
+ return true;
312
+ return !metadata?.liveRecognitionStrategy;
313
+ };
314
+ const getCapturePageRecognitionText = (metadata, extraText) => `${metadata?.liveRecognitionReason ?? ''} ${metadata?.liveRecognitionPath ?? ''} ${extraText ?? ''}`
315
+ .trim()
316
+ .toLowerCase();
317
+ exports.getCapturePageRecognitionText = getCapturePageRecognitionText;
318
+ const hasTrustedMrzCaptureEvidence = (metadata, recognitionText) => {
319
+ if (!metadata)
320
+ return false;
321
+ if (metadata.liveMrzUnexpectedForSide === true)
322
+ return false;
323
+ if (metadata.liveMrzCheckDigitsValid === false || metadata.liveMrzValidated === false) {
324
+ return false;
325
+ }
326
+ if (metadata.liveBoundsReliable === false && metadata.liveMrzValidated !== true)
327
+ return false;
328
+ if ((metadata.liveMrzConfidence ?? 0) < CAPTURE_CANDIDATE_MRZ_CONFIRMED_CONFIDENCE_MIN) {
329
+ return false;
330
+ }
331
+ if (metadata.liveMrzValidated === true)
332
+ return true;
333
+ if (metadata.liveMrzCheckDigitsValid === true)
334
+ return false;
335
+ const text = (0, exports.getCapturePageRecognitionText)(metadata, recognitionText);
336
+ return metadata.liveRecognitionAccepted === true && text.includes('mrz');
337
+ };
338
+ exports.hasTrustedMrzCaptureEvidence = hasTrustedMrzCaptureEvidence;
339
+ const hasTrustedCardFrontRecognitionReason = (metadata) => {
340
+ if (!isCardFrontCaptureContext(metadata))
341
+ return true;
342
+ const reason = normalizeEvidenceKey(metadata?.liveRecognitionReason);
343
+ if (reason === 'quad' || reason === 'classifier')
344
+ return true;
345
+ if (isFiniteNumber(metadata?.classifierConfidence) &&
346
+ metadata.classifierConfidence >= CAPTURE_CANDIDATE_HIGH_CLASSIFIER_CONFIDENCE) {
347
+ return true;
348
+ }
349
+ return (0, exports.hasTrustedMrzCaptureEvidence)(metadata);
350
+ };
351
+ exports.hasTrustedCardFrontRecognitionReason = hasTrustedCardFrontRecognitionReason;
352
+ const hasReadyCaptureCandidateDecision = (metadata) => metadata?.captureCandidateDecision === 'review_candidate' ||
353
+ metadata?.captureCandidateDecision === 'manual_ready' ||
354
+ metadata?.captureCandidateDecision === 'auto_ready';
355
+ exports.hasReadyCaptureCandidateDecision = hasReadyCaptureCandidateDecision;
356
+ const hasReadySafeCardLiveEvidence = (metadata) => {
357
+ if (!metadata)
358
+ return false;
359
+ if (metadata.cropShapeFamily !== 'card-like')
360
+ return false;
361
+ const hasReadyCandidateDecision = (0, exports.hasReadyCaptureCandidateDecision)(metadata);
362
+ const hasAcceptedLiveRecognition = metadata.liveRecognitionAccepted === true;
363
+ if (!hasReadyCandidateDecision && !hasAcceptedLiveRecognition)
364
+ return false;
365
+ if (!(0, exports.hasTrustedCardFrontRecognitionReason)(metadata))
366
+ return false;
367
+ return (0, exports.hasMarginSafeReliableCaptureQuad)(metadata);
368
+ };
369
+ exports.hasReadySafeCardLiveEvidence = hasReadySafeCardLiveEvidence;
370
+ const hasHardCaptureCutOffEvidence = (metadata) => Boolean(metadata?.clamped === true ||
371
+ metadata?.unsafeReason === 'clamped_edge' ||
372
+ (0, exports.hasUnsafeInsetReliableCaptureQuad)(metadata));
373
+ exports.hasHardCaptureCutOffEvidence = hasHardCaptureCutOffEvidence;
374
+ const isNativeViewfinderCaptureReason = (reason) => {
375
+ const normalized = String(reason || '').toLowerCase();
376
+ return (normalized === 'web_native_viewfinder_capture' ||
377
+ normalized.startsWith('web_native_viewfinder_capture_'));
378
+ };
379
+ const isNativeOverfullReason = (reason) => {
380
+ const normalized = String(reason || '').toLowerCase();
381
+ return (isNativeViewfinderCaptureReason(normalized) &&
382
+ (normalized.includes('too_large') ||
383
+ normalized.includes('nearly_full_frame') ||
384
+ normalized.includes('overfull') ||
385
+ normalized.includes('full_frame')));
386
+ };
387
+ const hasHighAreaNativeViewfinderEvidence = (metadata) => Boolean(isNativeViewfinderCaptureReason(metadata?.reason) &&
388
+ isFiniteNumber(metadata?.areaRatio) &&
389
+ metadata.areaRatio >= NATIVE_OVERFULL_AREA_RATIO_MIN);
390
+ const hasNativeOverfullCaptureRisk = (metadata) => Boolean(isNativeViewfinderCaptureReason(metadata?.reason) &&
391
+ (metadata?.unsafeReason === 'too_large' ||
392
+ metadata?.unsafeReason === 'nearly_full_frame' ||
393
+ isNativeOverfullReason(metadata?.reason) ||
394
+ hasHighAreaNativeViewfinderEvidence(metadata)));
395
+ exports.hasNativeOverfullCaptureRisk = hasNativeOverfullCaptureRisk;
396
+ const hasNativeClampedCaptureRisk = (metadata) => Boolean(isNativeViewfinderCaptureReason(metadata?.reason) &&
397
+ (metadata?.clamped === true || metadata?.unsafeReason === 'clamped_edge'));
398
+ exports.hasNativeClampedCaptureRisk = hasNativeClampedCaptureRisk;
399
+ const canTrustReadySafeCardEvidenceOverNativeUnsafeRisk = (metadata) => ((0, exports.hasReadySafeCardLiveEvidence)(metadata) &&
400
+ !(0, exports.hasHardCaptureCutOffEvidence)(metadata) &&
401
+ (0, exports.hasNativeOverfullCaptureRisk)(metadata)) ||
402
+ ((0, exports.hasReadySafeCardLiveEvidence)(metadata) &&
403
+ (0, exports.hasNativeClampedCaptureRisk)(metadata) &&
404
+ !(0, exports.hasUnsafeInsetReliableCaptureQuad)(metadata));
405
+ exports.canTrustReadySafeCardEvidenceOverNativeUnsafeRisk = canTrustReadySafeCardEvidenceOverNativeUnsafeRisk;
406
+ const hasReadySafeCardCropEvidence = (metadata) => !(0, exports.hasCaptureDocumentCutOffEvidence)(metadata) &&
407
+ !(0, exports.hasHardCaptureCutOffEvidence)(metadata) &&
408
+ isDocumentCropGeometrySafe(metadata) &&
409
+ (0, exports.hasReadySafeCardLiveEvidence)(metadata) &&
410
+ (metadata.cutOffRisk !== 'high' || (0, exports.canTrustReadySafeCardEvidenceOverNativeUnsafeRisk)(metadata));
411
+ const isDocumentCropVerified = (metadata) => metadata.applied === true &&
412
+ ((metadata.cropVerified === true &&
413
+ !isMeasuredCardViewfinderCrop(metadata) &&
414
+ !isReviewAutoCrop(metadata)) ||
415
+ (metadata.cornersOk === true && !isReviewAutoCrop(metadata)) ||
416
+ hasReadySafeCardCropEvidence(metadata) ||
417
+ (metadata.cornersOk !== false && (0, exports.hasMarginSafeDocumentCropEvidence)(metadata)));
418
+ exports.isDocumentCropVerified = isDocumentCropVerified;
419
+ const isSafeMeasuredNativeCrop = (metadata) => isNativeViewfinderCrop(metadata) &&
420
+ (0, exports.isDocumentCropVerified)(metadata) &&
421
+ isDocumentCropGeometrySafe(metadata);
422
+ const getDocumentCropRejectedReason = (metadata) => {
423
+ if (hasQuietZoneClampCutOffEvidence(metadata))
424
+ return 'quiet_zone_clamped';
425
+ if (metadata.unsafeReason)
426
+ return metadata.unsafeReason;
427
+ if (metadata.clamped)
428
+ return 'clamped_edge';
429
+ const safetyInputs = getCropSafetyInputs(metadata);
430
+ if (!safetyInputs)
431
+ return 'unsafe_crop_rejected';
432
+ const { widthRatio, heightRatio, leftMarginRatio, topMarginRatio, rightMarginRatio, bottomMarginRatio, areaRatio, aspectRatio, shapeFamily, } = safetyInputs;
433
+ const safetyThresholds = (0, exports.getDocumentCropSafetyThresholds)(aspectRatio, shapeFamily);
434
+ const minAreaRatio = getCropMinAreaRatio(metadata, aspectRatio, shapeFamily);
435
+ if (!isFiniteNumber(aspectRatio) ||
436
+ widthRatio < safetyThresholds.minWidthRatio ||
437
+ heightRatio < safetyThresholds.minHeightRatio ||
438
+ !(0, exports.isDocumentCropAspectSafe)(aspectRatio, safetyThresholds)) {
439
+ return 'crop_too_narrow';
440
+ }
441
+ if (leftMarginRatio < 0 ||
442
+ rightMarginRatio < 0 ||
443
+ topMarginRatio < 0 ||
444
+ bottomMarginRatio < 0 ||
445
+ leftMarginRatio > safetyThresholds.maxHorizontalOutsideMarginRatio ||
446
+ rightMarginRatio > safetyThresholds.maxHorizontalOutsideMarginRatio ||
447
+ topMarginRatio > safetyThresholds.maxVerticalOutsideMarginRatio ||
448
+ bottomMarginRatio > safetyThresholds.maxVerticalOutsideMarginRatio) {
449
+ return 'unsafe_crop_rejected';
450
+ }
451
+ if (areaRatio < minAreaRatio)
452
+ return 'crop_too_small';
453
+ return 'unsafe_crop_rejected';
454
+ };
455
+ exports.getDocumentCropRejectedReason = getDocumentCropRejectedReason;
456
+ const getCropDefaultConfidenceThreshold = (cropMetadata) => cropMetadata.method === 'perspective'
457
+ ? exports.DEFAULT_CROP_CONFIDENCE_THRESHOLD
458
+ : exports.DEFAULT_RECTANGLE_CROP_CONFIDENCE_THRESHOLD;
459
+ const buildCropArbitrationAssessment = ({ decision, policy, score, reason, reviewSafe, defaultSafe = false, cropVerified = false, cropSelectable = false, }) => ({
460
+ decision,
461
+ policy,
462
+ score,
463
+ reason,
464
+ reviewSafe,
465
+ defaultSafe,
466
+ cropVerified,
467
+ cropSelectable,
468
+ });
469
+ const hasDefaultableCropOutputDetail = (cropMetadata) => {
470
+ const size = cropMetadata.outputImageSize;
471
+ if (!size)
472
+ return true;
473
+ const trustedAnchoredCrop = isTrustedLiveBoundsCrop(cropMetadata) || isMrzAnchoredPassportDataPageCrop(cropMetadata);
474
+ const minLongSide = trustedAnchoredCrop
475
+ ? TRUSTED_LIVE_BOUNDS_CROP_OUTPUT_MIN_LONG_SIDE
476
+ : DEFAULT_CROP_OUTPUT_MIN_LONG_SIDE;
477
+ const minShortSide = trustedAnchoredCrop
478
+ ? TRUSTED_LIVE_BOUNDS_CROP_OUTPUT_MIN_SHORT_SIDE
479
+ : DEFAULT_CROP_OUTPUT_MIN_SHORT_SIDE;
480
+ return (Math.max(size.width, size.height) >= minLongSide &&
481
+ Math.min(size.width, size.height) >= minShortSide);
482
+ };
483
+ const hasDefaultSubmitInsetEvidence = (metadata) => {
484
+ const minimumInsetRatio = getMinimumCropBoundsInsetRatio(metadata);
485
+ return (minimumInsetRatio !== null && minimumInsetRatio >= DEFAULT_SUBMIT_CROP_VISIBLE_EDGE_INSET_RATIO);
486
+ };
487
+ function assessDocumentCropCandidate(croppedBase64, cropMetadata) {
488
+ if (!cropMetadata || !croppedBase64 || !cropMetadata.applied) {
489
+ return buildCropArbitrationAssessment({
490
+ decision: 'unsafe_crop',
491
+ policy: 'disabled_unsafe',
492
+ score: 0,
493
+ reason: !cropMetadata ? 'missing_crop_metadata' : 'crop_not_applied',
494
+ reviewSafe: false,
495
+ defaultSafe: false,
496
+ cropVerified: false,
497
+ cropSelectable: false,
498
+ });
499
+ }
500
+ if ((0, exports.hasCaptureDocumentCutOffEvidence)(cropMetadata) ||
501
+ hasQuietZoneClampCutOffEvidence(cropMetadata)) {
502
+ return buildCropArbitrationAssessment({
503
+ decision: 'unsafe_crop',
504
+ policy: 'disabled_unsafe',
505
+ score: Math.max(0, Math.round((cropMetadata.confidence ?? 0) * 20)),
506
+ reason: hasQuietZoneClampCutOffEvidence(cropMetadata)
507
+ ? 'quiet_zone_clamped'
508
+ : cropMetadata.reason || 'explicit_cutoff_evidence',
509
+ reviewSafe: false,
510
+ defaultSafe: false,
511
+ cropVerified: false,
512
+ cropSelectable: false,
513
+ });
514
+ }
515
+ if (hasUnsafeLiveQuadCropEvidence(cropMetadata)) {
516
+ return buildCropArbitrationAssessment({
517
+ decision: 'unsafe_crop',
518
+ policy: 'disabled_unsafe',
519
+ score: Math.max(0, Math.round((cropMetadata.confidence ?? 0) * 25)),
520
+ reason: 'edge_tight_live_quad',
521
+ reviewSafe: false,
522
+ defaultSafe: false,
523
+ cropVerified: false,
524
+ cropSelectable: false,
525
+ });
526
+ }
527
+ if (cropMetadata.cropSelectable === false && !isNativeViewfinderCrop(cropMetadata)) {
528
+ return buildCropArbitrationAssessment({
529
+ decision: 'unsafe_crop',
530
+ policy: 'disabled_unsafe',
531
+ score: Math.max(0, Math.round((cropMetadata.confidence ?? 0) * 35)),
532
+ reason: 'crop_marked_unselectable',
533
+ reviewSafe: false,
534
+ defaultSafe: false,
535
+ cropVerified: false,
536
+ cropSelectable: false,
537
+ });
538
+ }
539
+ if (!hasDocumentCropGeometry(cropMetadata)) {
540
+ return buildCropArbitrationAssessment({
541
+ decision: 'original_only',
542
+ policy: 'original_only',
543
+ score: Math.max(0, Math.round((cropMetadata.confidence ?? 0) * 45)),
544
+ reason: 'missing_crop_geometry',
545
+ reviewSafe: false,
546
+ defaultSafe: false,
547
+ cropVerified: false,
548
+ cropSelectable: false,
549
+ });
550
+ }
551
+ if (!isDocumentCropGeometrySafe(cropMetadata)) {
552
+ return buildCropArbitrationAssessment({
553
+ decision: 'unsafe_crop',
554
+ policy: 'disabled_unsafe',
555
+ score: Math.max(0, Math.round((cropMetadata.confidence ?? 0) * 35)),
556
+ reason: (0, exports.getDocumentCropRejectedReason)(cropMetadata),
557
+ reviewSafe: false,
558
+ defaultSafe: false,
559
+ cropVerified: false,
560
+ cropSelectable: false,
561
+ });
562
+ }
563
+ const hasTrustedCornerGeometry = (0, exports.isDocumentCropVerified)(cropMetadata) || isSafeMeasuredNativeCrop(cropMetadata);
564
+ if (cropMetadata.cornersOk === false) {
565
+ return buildCropArbitrationAssessment({
566
+ decision: 'original_with_overlay',
567
+ policy: 'original_only',
568
+ score: Math.max(0, Math.round((cropMetadata.confidence ?? 0) * 55)),
569
+ reason: 'corners_not_verified',
570
+ reviewSafe: true,
571
+ defaultSafe: false,
572
+ cropVerified: false,
573
+ cropSelectable: false,
574
+ });
575
+ }
576
+ if (!hasDefaultableCropOutputDetail(cropMetadata)) {
577
+ return buildCropArbitrationAssessment({
578
+ decision: 'original_with_overlay',
579
+ policy: 'original_only',
580
+ score: Math.max(0, Math.round((cropMetadata.confidence ?? 0) * 65)),
581
+ reason: 'crop_output_too_small',
582
+ reviewSafe: true,
583
+ defaultSafe: false,
584
+ cropVerified: hasTrustedCornerGeometry,
585
+ cropSelectable: false,
586
+ });
587
+ }
588
+ if (exports.DEFAULT_SUBMIT_CROP_INSET_FLOOR_ENABLED &&
589
+ hasTrustedCornerGeometry &&
590
+ !hasDefaultSubmitInsetEvidence(cropMetadata)) {
591
+ return buildCropArbitrationAssessment({
592
+ decision: 'original_with_overlay',
593
+ policy: 'original_only',
594
+ score: Math.max(0, Math.round((cropMetadata.confidence ?? 0) * 65)),
595
+ reason: 'crop_inset_too_small',
596
+ reviewSafe: true,
597
+ defaultSafe: false,
598
+ cropVerified: hasTrustedCornerGeometry,
599
+ cropSelectable: false,
600
+ });
601
+ }
602
+ if (isSafeMeasuredNativeCrop(cropMetadata)) {
603
+ return buildCropArbitrationAssessment({
604
+ decision: 'default_crop',
605
+ policy: 'default',
606
+ score: Math.max(80, Math.round((cropMetadata.confidence ?? 0.55) * 100)),
607
+ reason: 'safe_measured_native_crop',
608
+ reviewSafe: true,
609
+ defaultSafe: true,
610
+ cropVerified: true,
611
+ cropSelectable: true,
612
+ });
613
+ }
614
+ if ((cropMetadata.confidence ?? 0) >= getCropDefaultConfidenceThreshold(cropMetadata) &&
615
+ hasTrustedCornerGeometry) {
616
+ return buildCropArbitrationAssessment({
617
+ decision: 'default_crop',
618
+ policy: 'default',
619
+ score: Math.max(80, Math.round((cropMetadata.confidence ?? 0) * 100)),
620
+ reason: 'safe_verified_crop',
621
+ reviewSafe: true,
622
+ defaultSafe: true,
623
+ cropVerified: true,
624
+ cropSelectable: true,
625
+ });
626
+ }
627
+ return buildCropArbitrationAssessment({
628
+ decision: 'original_with_overlay',
629
+ policy: 'original_only',
630
+ score: Math.max(0, Math.round((cropMetadata.confidence ?? 0) * 70)),
631
+ reason: hasTrustedCornerGeometry ? 'crop_confidence_too_low' : 'crop_not_verified',
632
+ reviewSafe: true,
633
+ defaultSafe: false,
634
+ cropVerified: hasTrustedCornerGeometry,
635
+ cropSelectable: false,
636
+ });
637
+ }
638
+ function getDocumentCropReviewPolicy(croppedBase64, cropMetadata) {
639
+ return assessDocumentCropCandidate(croppedBase64, cropMetadata).policy;
640
+ }
641
+ function getDocumentCropTrustTier(croppedBase64, cropMetadata, client = 'web') {
642
+ const assessment = assessDocumentCropCandidate(croppedBase64, cropMetadata);
643
+ return (0, capture_trust_js_1.decideCaptureTrust)({
644
+ context: {
645
+ client,
646
+ phase: 'capture',
647
+ captureSource: cropMetadata?.cropSource === 'native_platform_scanner'
648
+ ? 'native_platform_scanner'
649
+ : 'camera',
650
+ },
651
+ capture: {
652
+ hasMetrics: true,
653
+ documentCandidate: true,
654
+ reviewSafe: assessment.reviewSafe,
655
+ autoSafe: false,
656
+ },
657
+ crop: {
658
+ applied: cropMetadata?.applied,
659
+ provenance: cropMetadata?.cropProvenance ?? cropMetadata?.cropSource ?? null,
660
+ unsafe: assessment.decision === 'unsafe_crop' || assessment.policy === 'disabled_unsafe',
661
+ unsafeReason: cropMetadata?.unsafeReason ?? undefined,
662
+ clamped: cropMetadata?.clamped,
663
+ cutOffRisk: cropMetadata?.cutOffRisk,
664
+ aspectSafe: assessment.reason === 'crop_bad_aspect' ? false : undefined,
665
+ cornersOk: cropMetadata?.cornersOk,
666
+ cropVerified: assessment.cropVerified,
667
+ cropSelectable: assessment.cropSelectable,
668
+ footprintTrusted: assessment.defaultSafe,
669
+ renderedContentOk: assessment.defaultSafe || assessment.reviewSafe,
670
+ },
671
+ }).cropTrustTier;
672
+ }
673
+ //# sourceMappingURL=document-crop-arbitration.js.map