using System.Text.Json.Nodes;
namespace CareFix.Api.Ai;
/// Reads tool arguments forgivingly: models sometimes send "12" where 12 was expected.
public static class JsonArg
{
public static int? Int(JsonNode? node)
{
if (node is not JsonValue v) return null;
if (v.TryGetValue(out var i)) return i;
if (v.TryGetValue(out var s) && int.TryParse(s, out var parsed)) return parsed;
return null;
}
public static bool? Bool(JsonNode? node)
{
if (node is not JsonValue v) return null;
if (v.TryGetValue(out var b)) return b;
if (v.TryGetValue(out var s) && bool.TryParse(s, out var parsed)) return parsed;
return null;
}
}
public static class AiTools
{
public const string SystemPrompt = """
You are CareFix, the database support assistant for Caresoft HIS (a hospital information system on Microsoft SQL Server).
A Caresoft support engineer describes a data problem at one hospital in plain language (English, Hindi or Hinglish).
Your job: find the exact records, explain what is wrong, and propose a minimal, safe correction that a human will approve.
How to work
- Work only through your tools. Never guess table or column names: use search_schema, then describe_table.
- Before querying, make sure you have at least one identifier (UHID or registration no, IPD no, bill no, receipt no, item name and date).
If it is missing, call ask_engineer with one short, specific question.
- run_select takes one T-SQL SELECT on dbo tables. Filter with WHERE on specific identifiers or a narrow date range and select only the
columns you need. Results stop at 200 rows. Patient-identifying fields come back as [masked]; work with IDs instead.
- Check linked tables (describe_table lists them) so the data stays consistent. For example, if a bill detail is wrong, the bill header total,
receipts or ledger may also need correction. Put every needed change in ONE fix, or explain why a part cannot be done safely.
- Before proposing a fix, show the evidence: which rows are wrong and why, with their identifiers.
Proposing fixes
- propose_fix changes one column of one row per step, identified by a unique key column.
- Keep it minimal. Never delete rows: to remove a wrong or duplicate entry, use the table's cancel/status flag as described in the data dictionary.
- Do not change key columns. Do not change a column the data dictionary marks as not editable.
- If there is no safe way to fix it with these steps, say so and recommend escalation to the HIS development team.
- If a lock applies (Tally posted, discharge finalised, insurance claim submitted, ABDM pushed), say so clearly; senior approval will be needed.
- If the records conflict or you are unsure, stop and explain what you found and what a human should check. Do not guess.
After execution you will be asked to verify: run a SELECT that shows the corrected state, then call verify_fix.
Writing to the engineer
- Short, plain English. Use exact identifiers (bill no, item, date, amount).
- Do not paste raw SQL unless asked. Keep lists short.
""";
public static readonly JsonArray Definitions = (JsonArray)JsonNode.Parse("""
[
{
"name": "search_schema",
"description": "Find tables and columns in this hospital's database by meaning or name. Searches the Caresoft data dictionary and the hospital's schema. Use English HIS words such as BILL, RECEIPT, REFUND, IPD, OPD, PHARMACY, ISSUE, LAB, XRAY, DISCHARGE, DOCTOR.",
"input_schema": {
"type": "object",
"properties": { "query": { "type": "string", "description": "A few search words, e.g. 'pharmacy issue ipd'" } },
"required": ["query"]
}
},
{
"name": "describe_table",
"description": "Get all columns of a table with types, key columns, meanings, value codes, editability, linked tables and business rules.",
"input_schema": {
"type": "object",
"properties": { "table": { "type": "string" } },
"required": ["table"]
}
},
{
"name": "run_select",
"description": "Run ONE read-only T-SQL SELECT on the hospital database (dbo tables only). Max 200 rows, 30 second timeout, patient-identifying fields masked.",
"input_schema": {
"type": "object",
"properties": {
"sql": { "type": "string", "description": "A single SELECT statement" },
"purpose": { "type": "string", "description": "One line: what this query checks" }
},
"required": ["sql", "purpose"]
}
},
{
"name": "get_playbook",
"description": "Load a known Caresoft fix pattern (diagnosis query and fix guidance) by id.",
"input_schema": {
"type": "object",
"properties": { "playbook_id": { "type": "integer" } },
"required": ["playbook_id"]
}
},
{
"name": "propose_fix",
"description": "Submit a data correction for human approval. It is NOT executed by this call. Each step changes one column of one row identified by a unique key. The server reads the current value itself and computes risk and approvals.",
"input_schema": {
"type": "object",
"properties": {
"summary": { "type": "string", "description": "1-2 sentences: what is wrong and what the fix does" },
"evidence": { "type": "string", "description": "The rows found and why they are wrong, with identifiers" },
"steps": {
"type": "array",
"items": {
"type": "object",
"properties": {
"table": { "type": "string" },
"pk_column": { "type": "string", "description": "Column that uniquely identifies the row" },
"pk_value": { "type": "string" },
"column": { "type": "string", "description": "Column to change" },
"new_value": { "type": ["string", "null"], "description": "New value as text (ISO format for dates), or null" },
"reason": { "type": "string" }
},
"required": ["table", "pk_column", "pk_value", "column", "new_value", "reason"]
}
}
},
"required": ["summary", "evidence", "steps"]
}
},
{
"name": "ask_engineer",
"description": "Ask the support engineer for a missing detail. Your turn ends until they reply.",
"input_schema": {
"type": "object",
"properties": { "question": { "type": "string" } },
"required": ["question"]
}
},
{
"name": "verify_fix",
"description": "Record whether an executed fix resolved the issue, after checking the data with run_select.",
"input_schema": {
"type": "object",
"properties": {
"fix_id": { "type": "integer" },
"resolved": { "type": "boolean" },
"note": { "type": "string", "description": "What you checked and what you saw" }
},
"required": ["fix_id", "resolved", "note"]
},
"cache_control": { "type": "ephemeral" }
}
]
""")!;
}