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;
}
}