BlueFin / backend /src /services /officerNotesService.js
vedanshmadan21's picture
Deploy Intelli-Credit to HuggingFace Spaces
7f6dd09
import axios from "axios";
import config from "../config.js";
import supabase from "../lib/supabase.js";
import { getJob, updateJobStatus } from "./jobService.js";
const INJECTION_KEYWORDS = [
"ignore",
"override",
"set score",
"approve this",
"reject this",
"system prompt",
"you are now",
"disregard",
"forget previous",
"new instructions",
"jailbreak",
"ignore all",
"ignore previous",
];
function computeDecision(score) {
if (score >= 75) return "APPROVE";
if (score >= 55) return "CONDITIONAL";
return "REJECT";
}
async function processOfficerNotes(jobId, rawNotes, officerId) {
// Step A β€” Sanitize
let sanitizedNotes = rawNotes.trim();
if (sanitizedNotes.length > 2000) {
throw new Error("Notes too long. Maximum 2000 characters.");
}
// Strip any HTML tags
sanitizedNotes = sanitizedNotes.replace(/<[^>]*>/g, "");
// Step B β€” Injection detection
const lower = sanitizedNotes.toLowerCase();
const injectionFound = INJECTION_KEYWORDS.some((kw) => lower.includes(kw));
if (injectionFound) {
// Log to audit_log
await supabase.from("audit_log").insert({
job_id: jobId,
event_type: "INJECTION_ATTEMPT",
event_data: { officer_id: officerId, raw_text: rawNotes },
});
// Log to officer_notes
await supabase.from("officer_notes").insert({
job_id: jobId,
raw_text: rawNotes,
injection_detected: true,
score_delta: -50,
officer_id: officerId,
});
// Fetch current score so we can report before/after
const job = await getJob(jobId);
const currentScore = job?.result?.score_breakdown?.final_score ?? 0;
const currentDecision = job?.result?.score_breakdown?.decision ?? "REJECT";
const newScore = Math.max(0, currentScore - 50);
return {
injection_detected: true,
injection_message:
"Prompt injection attempt detected. Penalty: -50 points. Incident logged to compliance.",
score_before: currentScore,
score_delta: -50,
score_after: newScore,
decision_before: currentDecision,
decision_after: computeDecision(newScore),
adjustments: [],
interpretation: "Security violation detected.",
};
}
// Step C β€” Fetch current score from job
const job = await getJob(jobId);
if (!job) throw new Error("Job not found");
const currentScore = job.result?.score_breakdown?.final_score ?? 0;
const currentDecision = job.result?.score_breakdown?.decision ?? "REJECT";
// Step D β€” Call Python AI service
const aiRes = await axios.post(
`${config.aiServiceUrl}/api/v1/cam/officer-notes`,
{
job_id: jobId,
notes_text: sanitizedNotes,
officer_id: officerId,
},
{ timeout: 60000 },
);
const aiResponse = aiRes.data;
const scoreDelta = aiResponse.score_delta ?? 0;
// Step E β€” Update job result in Supabase
const newScore = Math.max(0, Math.min(100, currentScore + scoreDelta));
const newDecision = computeDecision(newScore);
const updatedResult = {
...job.result,
score_breakdown: {
...job.result.score_breakdown,
final_score: newScore,
decision: newDecision,
},
officer_notes_applied: true,
officer_score_delta: scoreDelta,
};
await updateJobStatus(jobId, "completed", updatedResult);
// Step F β€” Insert records
await supabase.from("officer_notes").insert({
job_id: jobId,
raw_text: sanitizedNotes,
injection_detected: false,
score_delta: scoreDelta,
officer_id: officerId,
});
await supabase.from("audit_log").insert({
job_id: jobId,
event_type: "OFFICER_NOTES_SUBMITTED",
event_data: {
officer_id: officerId,
score_delta: scoreDelta,
injection_detected: false,
},
});
// Step G β€” Return response
return {
injection_detected: false,
score_before: currentScore,
score_delta: scoreDelta,
score_after: newScore,
decision_before: currentDecision,
decision_after: newDecision,
adjustments: aiResponse.adjustments || [],
interpretation: aiResponse.interpretation || "",
};
}
export { processOfficerNotes };