using System.Text;
using System.Text.RegularExpressions;
using CareFix.Api.Infrastructure;
using Dapper;
using Microsoft.Extensions.Caching.Memory;
namespace CareFix.Api.Knowledge;
public sealed class ColumnInfo
{
public string ColumnName { get; set; } = "";
public string DataType { get; set; } = "";
public bool IsNullable { get; set; }
public bool IsPk { get; set; }
public string? Meaning { get; set; }
public string? ValueCodes { get; set; }
public bool? IsPii { get; set; }
public bool? Editable { get; set; }
public string? RiskLevel { get; set; }
}
public sealed class LockRule
{
public int RuleId { get; set; }
public string RuleText { get; set; } = "";
public string LockCheckSql { get; set; } = "";
public string? LockMessage { get; set; }
}
public sealed class PlaybookRow
{
public int PlaybookId { get; set; }
public string Title { get; set; } = "";
public string? IssueType { get; set; }
public string? Module { get; set; }
public string? Keywords { get; set; }
public string? Description { get; set; }
public string? DiagnosisSql { get; set; }
public string? FixGuidance { get; set; }
public string Risk { get; set; } = "Medium";
public int SuccessCount { get; set; }
}
/// Caresoft's data dictionary, rules, playbooks and per-hospital schema snapshot.
public sealed class KnowledgeBase(ControlDb db, IMemoryCache cache)
{
private sealed class HitRow
{
public string TableName { get; set; } = "";
public string ColumnName { get; set; } = "";
public string DataType { get; set; } = "";
public string? ColumnMeaning { get; set; }
public string? TableMeaning { get; set; }
public string? Module { get; set; }
public int Score { get; set; }
}
public void Invalidate(int? hospitalId = null)
{
if (hospitalId is not null) cache.Remove($"tables:{hospitalId}");
cache.Remove("pii");
}
public async Task> KnownTablesAsync(int hospitalId, CancellationToken ct)
{
return (await cache.GetOrCreateAsync($"tables:{hospitalId}", async e =>
{
e.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10);
await using var c = await db.OpenAsync(ct);
var t = await c.QueryAsync("SELECT DISTINCT TableName FROM CF_HOSPITAL_SCHEMA WHERE HospitalId = @hospitalId", new { hospitalId });
return new HashSet(t, StringComparer.OrdinalIgnoreCase);
}))!;
}
public async Task> PiiColumnsAsync(CancellationToken ct)
{
return (await cache.GetOrCreateAsync("pii", async e =>
{
e.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10);
await using var c = await db.OpenAsync(ct);
var cols = await c.QueryAsync("SELECT DISTINCT ColumnName FROM CF_KB_COLUMN WHERE IsPii = 1");
return new HashSet(cols, StringComparer.OrdinalIgnoreCase);
}))!;
}
public async Task ColumnInfoAsync(int hospitalId, string table, string column, CancellationToken ct)
{
await using var c = await db.OpenAsync(ct);
return await c.QuerySingleOrDefaultAsync("""
SELECT TOP 1 s.ColumnName, s.DataType, s.IsNullable, s.IsPk, k.Meaning, k.ValueCodes, k.IsPii, k.Editable, k.RiskLevel
FROM CF_HOSPITAL_SCHEMA s
LEFT JOIN CF_KB_COLUMN k ON k.TableName = s.TableName AND k.ColumnName = s.ColumnName
WHERE s.HospitalId = @hospitalId AND s.SchemaName = 'dbo' AND s.TableName = @table AND s.ColumnName = @column
""", new { hospitalId, table, column });
}
public async Task