@onco-foundry/mask-port 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/textin_masker.d.ts +2 -0
- package/dist/textin_masker.js +38 -3
- package/package.json +4 -4
package/dist/textin_masker.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ export type TextInMaskerOptions = {
|
|
|
23
23
|
readonly baseURL?: string;
|
|
24
24
|
readonly timeoutMs?: number;
|
|
25
25
|
readonly maxInputBytes?: number;
|
|
26
|
+
/** 自动处理链路使用:已发现的敏感词无法完整定位时拒绝交付。 */
|
|
27
|
+
readonly requireAllMatches?: boolean;
|
|
26
28
|
/** 测试注入缝:替换掉真实的 HTTP 层,不碰网络。 */
|
|
27
29
|
readonly fetch?: typeof fetch;
|
|
28
30
|
/**
|
package/dist/textin_masker.js
CHANGED
|
@@ -156,17 +156,28 @@ export const createTextInMasker = (options) => {
|
|
|
156
156
|
if (payload.data.code !== 200 || payload.data.result === undefined) {
|
|
157
157
|
throw new AppError(`TextIn 脱敏接口报错:${payload.data.code} ${payload.data.message}`, 502);
|
|
158
158
|
}
|
|
159
|
+
if (options.requireAllMatches && payload.data.result.pages.length !== 1) {
|
|
160
|
+
throw new AppError('TextIn 脱敏需要单页图片识别结果', 502);
|
|
161
|
+
}
|
|
159
162
|
const page = payload.data.result.pages[0];
|
|
160
163
|
const lines = page.lines;
|
|
161
164
|
const recognizedText = lines.map((line) => line.text).join('\n');
|
|
162
165
|
const scaleX = page.width !== undefined && page.width > 0 ? imageWidth / page.width : 1;
|
|
163
166
|
const scaleY = page.height !== undefined && page.height > 0 ? imageHeight / page.height : 1;
|
|
164
|
-
|
|
167
|
+
if (options.requireAllMatches && !recognizedText.trim()) {
|
|
168
|
+
throw new AppError('TextIn 脱敏未识别出文字,无法确认处理结果', 502);
|
|
169
|
+
}
|
|
170
|
+
const detected = await finder(recognizedText);
|
|
171
|
+
if (options.requireAllMatches && detected.some(word => !word.text || !(word.target in TARGET_LABELS) || !recognizedText.includes(word.text))) {
|
|
172
|
+
throw new AppError('TextIn 脱敏判定结果无法定位', 502);
|
|
173
|
+
}
|
|
174
|
+
const requested = detected
|
|
165
175
|
.filter((word) => word.text !== '' && request.targets.includes(word.target));
|
|
166
176
|
const finalQuads = [];
|
|
167
177
|
const mappings = [];
|
|
168
178
|
const counters = new Map();
|
|
169
179
|
for (const word of requested) {
|
|
180
|
+
let located = 0;
|
|
170
181
|
for (const line of lines) {
|
|
171
182
|
let fromIndex = 0;
|
|
172
183
|
while (true) {
|
|
@@ -174,9 +185,30 @@ export const createTextInMasker = (options) => {
|
|
|
174
185
|
if (hit < 0)
|
|
175
186
|
break;
|
|
176
187
|
fromIndex = hit + word.text.length;
|
|
177
|
-
const
|
|
178
|
-
|
|
188
|
+
const locatedLine = options.requireAllMatches && line.char_positions?.length !== line.text.length
|
|
189
|
+
? { ...line, char_positions: undefined } : line;
|
|
190
|
+
const quad = locateWordQuad(locatedLine, hit, word.text.length);
|
|
191
|
+
if (quad === undefined) {
|
|
192
|
+
if (options.requireAllMatches)
|
|
193
|
+
throw new AppError('TextIn 脱敏敏感词缺少坐标', 502);
|
|
179
194
|
continue;
|
|
195
|
+
}
|
|
196
|
+
if (options.requireAllMatches) {
|
|
197
|
+
const scaled = scaleQuadToImage(quad, scaleX, scaleY);
|
|
198
|
+
const xs = scaled.map(point => point[0]);
|
|
199
|
+
const ys = scaled.map(point => point[1]);
|
|
200
|
+
const area = Math.abs(scaled.reduce((sum, [x, y], index) => {
|
|
201
|
+
const next = scaled[(index + 1) % scaled.length];
|
|
202
|
+
return sum + x * next[1] - next[0] * y;
|
|
203
|
+
}, 0));
|
|
204
|
+
if (area < 1 || scaled.flat().some(n => !Number.isFinite(n)) ||
|
|
205
|
+
Math.min(...xs) < 0 || Math.min(...ys) < 0 ||
|
|
206
|
+
Math.max(...xs) > imageWidth || Math.max(...ys) > imageHeight ||
|
|
207
|
+
Math.max(...xs) <= Math.min(...xs) || Math.max(...ys) <= Math.min(...ys)) {
|
|
208
|
+
throw new AppError('TextIn 脱敏坐标无效', 502);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
located++;
|
|
180
212
|
finalQuads.push(clampQuad(scaleQuad(scaleQuadToImage(quad, scaleX, scaleY), SAFETY_SCALE), imageWidth, imageHeight));
|
|
181
213
|
const count = (counters.get(word.target) ?? 0) + 1;
|
|
182
214
|
counters.set(word.target, count);
|
|
@@ -187,6 +219,9 @@ export const createTextInMasker = (options) => {
|
|
|
187
219
|
});
|
|
188
220
|
}
|
|
189
221
|
}
|
|
222
|
+
if (options.requireAllMatches && located === 0) {
|
|
223
|
+
throw new AppError('TextIn 脱敏敏感词无法在单行定位', 502);
|
|
224
|
+
}
|
|
190
225
|
}
|
|
191
226
|
const overlay = polygonSvg(imageWidth, imageHeight, finalQuads);
|
|
192
227
|
const uprightMasked = await sharp(upright.data)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onco-foundry/mask-port",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -16,10 +16,10 @@
|
|
|
16
16
|
"agent-lattice": "0.25.0",
|
|
17
17
|
"sharp": "^0.35.3",
|
|
18
18
|
"zod": "^4.4.3",
|
|
19
|
-
"@onco-foundry/
|
|
20
|
-
"@onco-foundry/trace-port": "0.3.2",
|
|
19
|
+
"@onco-foundry/capability-registry": "0.1.1",
|
|
21
20
|
"@onco-foundry/errors": "0.1.1",
|
|
22
|
-
"@onco-foundry/
|
|
21
|
+
"@onco-foundry/trace-port": "0.3.2",
|
|
22
|
+
"@onco-foundry/resource-versioning": "0.3.0"
|
|
23
23
|
},
|
|
24
24
|
"publishConfig": {
|
|
25
25
|
"access": "public"
|