using System.Text.RegularExpressions; namespace CareFix.Api.Security; /// Removes patient-identifying data before anything is sent to the AI or stored in query logs. public sealed partial class PiiMasker { public const string Mask = "[masked]"; private static readonly string[] ColumnContains = ["MOBILE", "PHONE", "CONTACTNO", "AADHA", "ADHAR", "ABHA", "ADDRESS", "EMAIL", "PASSPORT", "PANCARD"]; private static readonly string[] NameExact = ["PNAME", "FNAME", "MNAME", "LNAME", "PATNAME", "FIRSTNAME", "MIDDLENAME", "LASTNAME", "SURNAME"]; private static readonly string[] NameSuffix = ["PATIENTNAME", "FIRSTNAME", "MIDDLENAME", "LASTNAME", "FATHERNAME", "MOTHERNAME", "HUSBANDNAME", "SPOUSENAME", "RELATIVENAME", "KINNAME", "GUARDIANNAME"]; [GeneratedRegex(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}")] private static partial Regex Email(); [GeneratedRegex(@"(?? kbPiiColumns) { if (kbPiiColumns?.Contains(column) == true) return true; var n = column.Replace("_", "").ToUpperInvariant(); return ColumnContains.Any(n.Contains) || NameExact.Contains(n) || NameSuffix.Any(n.EndsWith); } /// Masks phone, Aadhaar, ABHA and e-mail patterns inside free text. public string MaskText(string s) { var r = Email().Replace(s, Mask); r = Abha().Replace(r, Mask); r = Aadhaar().Replace(r, Mask); return Mobile().Replace(r, Mask); } public object? MaskValue(string column, object? value, ISet? kbPiiColumns) { if (value is null) return null; if (IsPiiColumn(column, kbPiiColumns)) return Mask; // Pure numbers are usually IDs (bill no, UHID) that the AI needs; only free text is scanned. if (value is string s && s.Any(ch => char.IsLetter(ch) || ch == ' ')) return MaskText(s); return value; } }