70 lines
2.8 KiB
TypeScript
70 lines
2.8 KiB
TypeScript
const DISPLAY_PLACEHOLDERS: Record<string, string> = {
|
|
"[NAME]": "익명 내담자",
|
|
"[ORG]": "소속 기관",
|
|
"[PHONE]": "연락처",
|
|
"[EMAIL]": "이메일",
|
|
"[RRN]": "주민등록번호",
|
|
"[NUMID]": "식별번호",
|
|
"[DATE]": "날짜",
|
|
"[MONEY]": "금액",
|
|
"[ADDR]": "주소",
|
|
"[ADDRESS]": "주소",
|
|
};
|
|
|
|
function hasHangulBatchim(value: string) {
|
|
for (let index = value.length - 1; index >= 0; index -= 1) {
|
|
const code = value.charCodeAt(index);
|
|
if (code >= 0xac00 && code <= 0xd7a3) return (code - 0xac00) % 28 !== 0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function replacePlaceholderWithNaturalParticle(
|
|
text: string,
|
|
placeholder: string,
|
|
label: string,
|
|
) {
|
|
const batchim = hasHangulBatchim(label);
|
|
// `이고`를 단일 조사 `이`보다 먼저 처리하지 않으면
|
|
// `[NAME]이고`가 `익명 내담자가고`로 깨진다.
|
|
let next = text.split(`${placeholder}이고`).join(`${label}이고`);
|
|
for (const [variants, particle] of [
|
|
[["으로", "로"], batchim ? "으로" : "로"],
|
|
[["은", "는"], batchim ? "은" : "는"],
|
|
[["이", "가"], batchim ? "이" : "가"],
|
|
[["을", "를"], batchim ? "을" : "를"],
|
|
[["과", "와"], batchim ? "과" : "와"],
|
|
] as const) {
|
|
for (const variant of variants) {
|
|
next = next.split(`${placeholder}${variant}`).join(`${label}${particle}`);
|
|
}
|
|
}
|
|
return next.split(placeholder).join(label);
|
|
}
|
|
|
|
/**
|
|
* 저장/API의 privacy-proof 토큰은 유지하고, 사람이 읽는 일반 대화 표면에서만
|
|
* 토큰을 안전한 설명으로 낮춘다. 근거 인용·내보내기에는 사용하지 않는다.
|
|
*/
|
|
export function displayPiiSafeText(text: string) {
|
|
// 2026-08-09 이전 저장본에서 광범위한 성씨 휴리스틱이 평범한 용언을
|
|
// NAME으로 오탐한 두 실관측 문형. 원문은 복원할 수 없으므로 표시층에서만
|
|
// 결정적으로 복구하고 저장/API privacy proof는 그대로 둔다.
|
|
let next = text.replace(
|
|
/\[NAME\]는 건지\s+잘 \[NAME\]는데요/g,
|
|
"되는 건지 잘 모르겠는데요",
|
|
);
|
|
// 평가 문장에 이미 역할 명사가 있으면 이름 토큰을 다시 "익명 내담자"로
|
|
// 풀지 않는다. 특히 "내담자 [NAME]을 받을 여백"은 저장 마스킹 뒤
|
|
// 사람에게 "내담자 반응을 받을 여백"으로 보여야 한다.
|
|
next = next
|
|
.replace(/내담자\s+\[NAME\](?:을|를)/g, "내담자 반응을")
|
|
.replace(/내담자\s+\[NAME\](?:이|가)/g, "내담자가")
|
|
.replace(/내담자\s+\[NAME\](?:은|는)/g, "내담자는")
|
|
.replace(/내담자\s+\[NAME\]의/g, "내담자의")
|
|
.replace(/내담자\s+\[NAME\]/g, "내담자");
|
|
for (const [placeholder, label] of Object.entries(DISPLAY_PLACEHOLDERS)) {
|
|
next = replacePlaceholderWithNaturalParticle(next, placeholder, label);
|
|
}
|
|
return next;
|
|
}
|