added risk factor to quality audit functionality - risk factor is required when cheking quality audit box - both these fields are admin only

This commit is contained in:
michael 2025-09-05 14:04:18 -05:00
parent f811854198
commit 48ee1298e9
6 changed files with 258 additions and 5 deletions

View file

@ -147,6 +147,8 @@ async def create_agent(agent_data: dict, user_id: str):
agent_doc["quality_audit_updated_at"] = None
if "quality_audit_updated_by_name" not in agent_doc:
agent_doc["quality_audit_updated_by_name"] = None
if "risk_factor" not in agent_doc:
agent_doc["risk_factor"] = None
result = await agents_collection.insert_one(agent_doc)
agent_doc["_id"] = result.inserted_id
return agent_doc
@ -223,6 +225,10 @@ async def update_agent(agent_id: str, update_data: dict, user_id: str = None, ad
update_data["quality_audit_updated_by"] = admin_user_info.get("user_id")
update_data["quality_audit_updated_at"] = datetime.utcnow().isoformat()
update_data["quality_audit_updated_by_name"] = admin_user_info.get("user_name", admin_user_info.get("email"))
# If Quality Audit is being unchecked, clear Risk Factor
if update_data["quality_audit_status"] is False:
update_data["risk_factor"] = None
update_data["updated_at"] = datetime.utcnow()
result = await agents_collection.update_one(

21
main.py
View file

@ -141,6 +141,7 @@ def create_agent_response(agent: dict) -> models.AiAgentResponse:
quality_audit_updated_by=agent.get("quality_audit_updated_by"),
quality_audit_updated_at=agent.get("quality_audit_updated_at"),
quality_audit_updated_by_name=agent.get("quality_audit_updated_by_name"),
risk_factor=agent.get("risk_factor"),
created_by=agent["created_by"]
)
@ -466,7 +467,8 @@ async def agent_register_form(
agent_tags: str = Form(None),
agent_userbase: str = Form(None),
agent_capabilities: str = Form(None),
quality_audit_status: bool = Form(False)
quality_audit_status: bool = Form(False),
risk_factor: int = Form(None)
):
try:
# Get user from cookie - require authentication
@ -505,9 +507,18 @@ async def agent_register_form(
agent_data["quality_audit_updated_by"] = user_id
agent_data["quality_audit_updated_at"] = datetime.utcnow().isoformat()
agent_data["quality_audit_updated_by_name"] = current_user.get("full_name", current_user.get("email"))
# Validate Risk Factor when Quality Audit is checked
if risk_factor is None or not (1 <= risk_factor <= 5):
context = get_template_context(request, current_user)
context["error"] = "Risk Factor (1-5) is required when Quality Audit is checked."
return templates.TemplateResponse("agent_register.html", context)
agent_data["risk_factor"] = risk_factor
else:
# Non-admin or quality audit not checked
agent_data["quality_audit_status"] = False
agent_data["risk_factor"] = None
# Remove None values
agent_data = {k: v for k, v in agent_data.items() if v is not None}
@ -749,6 +760,14 @@ async def update_agent(agent_id: str, agent: models.AiAgentCreate, current_user:
admin_user_info = None
agent_data = agent.model_dump()
# Validate Risk Factor when Quality Audit is checked (admin only)
if current_user.get("is_admin") and agent_data.get("quality_audit_status"):
if agent_data.get("risk_factor") is None or not (1 <= agent_data.get("risk_factor", 0) <= 5):
raise HTTPException(
status_code=422,
detail="Risk Factor (1-5) is required when Quality Audit is checked."
)
if current_user.get("is_admin"):
admin_user_info = {
"user_id": str(current_user["_id"]),

View file

@ -23,6 +23,7 @@ class AiAgent(BaseModel):
quality_audit_updated_by: str | None = Field(default=None, title="Admin user ID who updated quality audit")
quality_audit_updated_at: str | None = Field(default=None, title="Quality audit last update timestamp")
quality_audit_updated_by_name: str | None = Field(default=None, title="Admin user name who updated quality audit")
risk_factor: int | None = Field(default=None, title="Risk factor rating (1-5)", ge=1, le=5)
@ -71,6 +72,7 @@ class AiAgentCreate(BaseModel):
quality_audit_updated_by: Optional[str] = None
quality_audit_updated_at: Optional[str] = None
quality_audit_updated_by_name: Optional[str] = None
risk_factor: Optional[int] = Field(default=None, ge=1, le=5)
class AiAgentResponse(BaseModel):
agent_id: str
@ -93,6 +95,7 @@ class AiAgentResponse(BaseModel):
quality_audit_updated_by: Optional[str] = None
quality_audit_updated_at: Optional[str] = None
quality_audit_updated_by_name: Optional[str] = None
risk_factor: Optional[int] = None
created_by: str
# Agent Collector API Models (for compatibility with agent_collector app)

View file

@ -306,7 +306,7 @@
<div class="mb-3" id="editQualityAuditSection">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="editQualityAuditStatus">
<input class="form-check-input" type="checkbox" id="editQualityAuditStatus" onchange="toggleAdminRiskFactor()">
<label class="form-check-label" for="editQualityAuditStatus">
<i class="fas fa-certificate me-2"></i>Quality Audit
<span class="badge bg-warning text-dark ms-2">Admin Only</span>
@ -316,6 +316,25 @@
Check this box if the agent has passed quality audit review.
</div>
</div>
<div class="mb-3" id="editRiskFactorSection" style="display: none;">
<label for="editRiskFactor" class="form-label">
<i class="fas fa-exclamation-triangle me-2"></i>Risk Factor
<span class="badge bg-warning text-dark ms-2">Admin Only</span>
<span class="text-danger">*</span>
</label>
<select class="form-select" id="editRiskFactor">
<option value="">Select Risk Level</option>
<option value="1">1 - Very Low Risk</option>
<option value="2">2 - Low Risk</option>
<option value="3">3 - Medium Risk</option>
<option value="4">4 - High Risk</option>
<option value="5">5 - Very High Risk</option>
</select>
<div class="form-text" id="editRiskFactorNote">
Required when Quality Audit is checked. Select the appropriate risk level for this agent.
</div>
</div>
</form>
</div>
<div class="modal-footer">
@ -421,6 +440,11 @@
.status-Inactive { background-color: #f8d7da; color: #721c24; }
.status-Deprecated { background-color: #e2e3e5; color: #41464b; }
.bg-orange {
background-color: #fd7e14 !important;
color: white !important;
}
@media (max-width: 768px) {
.stat-card {
text-align: center;
@ -769,6 +793,32 @@ function formatDate(dateString) {
return date.toLocaleDateString();
}
function getRiskFactorLabel(riskFactor) {
const labels = {
1: '1 - Very Low Risk',
2: '2 - Low Risk',
3: '3 - Medium Risk',
4: '4 - High Risk',
5: '5 - Very High Risk'
};
return labels[riskFactor] || 'Not Set';
}
function toggleAdminRiskFactor() {
const qualityAuditStatus = document.getElementById('editQualityAuditStatus');
const riskFactorSection = document.getElementById('editRiskFactorSection');
const riskFactor = document.getElementById('editRiskFactor');
if (qualityAuditStatus.checked) {
riskFactorSection.style.display = 'block';
riskFactor.required = true;
} else {
riskFactorSection.style.display = 'none';
riskFactor.required = false;
riskFactor.value = '';
}
}
function showSuccess(message) {
alert(message);
}
@ -870,6 +920,19 @@ async function editAgentAdmin(agentId) {
}
document.getElementById('editQualityAuditNote').innerHTML = noteHtml;
// Handle Risk Factor field
const riskFactorSection = document.getElementById('editRiskFactorSection');
const riskFactor = document.getElementById('editRiskFactor');
riskFactor.value = agent.risk_factor || '';
if (agent.quality_audit_status) {
riskFactorSection.style.display = 'block';
riskFactor.required = true;
} else {
riskFactorSection.style.display = 'none';
riskFactor.required = false;
}
// Show edit modal
const modal = new bootstrap.Modal(document.getElementById('editAgentModal'));
modal.show();
@ -900,6 +963,16 @@ async function handleEditAgentSubmit(e) {
quality_audit_status: document.getElementById('editQualityAuditStatus').checked
};
// Add Risk Factor
const riskFactorValue = document.getElementById('editRiskFactor').value;
agentData.risk_factor = riskFactorValue ? parseInt(riskFactorValue) : null;
// Validate Risk Factor if Quality Audit is checked
if (agentData.quality_audit_status && (!agentData.risk_factor || agentData.risk_factor < 1 || agentData.risk_factor > 5)) {
showError('Risk Factor is required when Quality Audit is checked!');
return;
}
try {
const response = await fetch(`{{ base_path }}/api/agents/${agentId}`, {
method: 'PUT',

View file

@ -305,7 +305,7 @@
<div class="mb-3" id="editQualityAuditSection">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="editQualityAuditStatus">
<input class="form-check-input" type="checkbox" id="editQualityAuditStatus" onchange="toggleEditRiskFactor()">
<label class="form-check-label" for="editQualityAuditStatus">
<i class="fas fa-certificate me-2"></i>Quality Audit
<span class="badge bg-warning text-dark ms-2">Admin Only</span>
@ -315,6 +315,25 @@
Only administrators can modify quality audit status.
</div>
</div>
<div class="mb-3" id="editRiskFactorSection" style="display: none;">
<label for="editRiskFactor" class="form-label">
<i class="fas fa-exclamation-triangle me-2"></i>Risk Factor
<span class="badge bg-warning text-dark ms-2">Admin Only</span>
<span class="text-danger">*</span>
</label>
<select class="form-select" id="editRiskFactor">
<option value="">Select Risk Level</option>
<option value="1">1 - Very Low Risk</option>
<option value="2">2 - Low Risk</option>
<option value="3">3 - Medium Risk</option>
<option value="4">4 - High Risk</option>
<option value="5">5 - Very High Risk</option>
</select>
<div class="form-text" id="editRiskFactorNote">
Required when Quality Audit is checked. Select the appropriate risk level for this agent.
</div>
</div>
</form>
</div>
<div class="modal-footer">
@ -351,6 +370,11 @@
.status-Inactive { background-color: #f8d7da; color: #721c24; }
.status-Deprecated { background-color: #e2e3e5; color: #41464b; }
.bg-orange {
background-color: #fd7e14 !important;
color: white !important;
}
.agent-icon {
width: 50px;
height: 50px;
@ -562,6 +586,7 @@ function displayAgents(agentsToShow) {
</span>
${agent.agent_version ? `<span class="badge bg-light text-dark">v${agent.agent_version}</span>` : ''}
${agent.quality_audit_status ? '<span class="badge bg-success" title="Quality Audited"><i class="fas fa-certificate"></i></span>' : ''}
${agent.quality_audit_status && agent.risk_factor ? getRiskFactorBadge(agent.risk_factor) : ''}
</div>
</div>
<div class="text-end">
@ -639,6 +664,7 @@ async function showAgentDetails(agentId) {
'<span class="badge bg-success"><i class="fas fa-certificate me-1"></i>Audited</span>' :
'<span class="badge bg-secondary">Not Audited</span>'
}
${agent.quality_audit_status && agent.risk_factor ? getRiskFactorBadge(agent.risk_factor) : ''}
</td></tr>
</table>
</div>
@ -828,6 +854,8 @@ async function showEditModal() {
// Handle Quality Audit field
const qualityAuditCheckbox = document.getElementById('editQualityAuditStatus');
const qualityAuditSection = document.getElementById('editQualityAuditSection');
const riskFactorSection = document.getElementById('editRiskFactorSection');
const riskFactor = document.getElementById('editRiskFactor');
if (currentUserIsAdmin) {
qualityAuditSection.style.display = 'block';
@ -844,8 +872,19 @@ async function showEditModal() {
noteHtml += '<br><br><small class="text-muted"><i class="fas fa-info-circle me-1"></i>No quality audit changes recorded yet.</small>';
}
document.getElementById('editQualityAuditNote').innerHTML = noteHtml;
// Handle Risk Factor field
riskFactor.value = agent.risk_factor || '';
if (agent.quality_audit_status) {
riskFactorSection.style.display = 'block';
riskFactor.required = true;
} else {
riskFactorSection.style.display = 'none';
riskFactor.required = false;
}
} else {
qualityAuditSection.style.display = 'none';
riskFactorSection.style.display = 'none';
}
// Hide details modal if it's open, then show edit modal
@ -884,9 +923,16 @@ async function updateAgent(e) {
agent_capabilities: document.getElementById('editAgentCapabilities').value.split(',').map(s => s.trim()).filter(s => s)
};
// Add Quality Audit status if user is admin
// Add Quality Audit status and Risk Factor if user is admin
if (currentUserIsAdmin) {
agentData.quality_audit_status = document.getElementById('editQualityAuditStatus').checked;
const riskFactorValue = document.getElementById('editRiskFactor').value;
agentData.risk_factor = riskFactorValue ? parseInt(riskFactorValue) : null;
}
// Validate the form before sending
if (!validateEditForm()) {
return;
}
console.log('Sending agent update data:', agentData);
@ -1000,6 +1046,31 @@ function formatDate(dateString) {
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
}
function getRiskFactorLabel(riskFactor) {
const labels = {
1: '1 - Very Low Risk',
2: '2 - Low Risk',
3: '3 - Medium Risk',
4: '4 - High Risk',
5: '5 - Very High Risk'
};
return labels[riskFactor] || 'Not Set';
}
function getRiskFactorBadge(riskFactor) {
if (!riskFactor) return '';
const colors = {
1: 'success', // Very Low Risk - Green
2: 'info', // Low Risk - Blue
3: 'warning', // Medium Risk - Yellow
4: 'orange', // High Risk - Orange (we'll style this)
5: 'danger' // Very High Risk - Red
};
return `<span class="badge bg-${colors[riskFactor] || 'secondary'} ms-1" title="${getRiskFactorLabel(riskFactor)}">Risk ${riskFactor}</span>`;
}
function showSuccess(message) {
// Simple alert - could be replaced with a toast notification
alert(message);
@ -1010,6 +1081,36 @@ function showError(message) {
alert('Error: ' + message);
}
function toggleEditRiskFactor() {
const qualityAuditStatus = document.getElementById('editQualityAuditStatus');
const riskFactorSection = document.getElementById('editRiskFactorSection');
const riskFactor = document.getElementById('editRiskFactor');
if (qualityAuditStatus.checked && currentUserIsAdmin) {
riskFactorSection.style.display = 'block';
riskFactor.required = true;
} else {
riskFactorSection.style.display = 'none';
riskFactor.required = false;
if (!qualityAuditStatus.checked) {
riskFactor.value = '';
}
}
}
function validateEditForm() {
const qualityAuditStatus = document.getElementById('editQualityAuditStatus');
const riskFactor = document.getElementById('editRiskFactor');
// Validate Risk Factor if Quality Audit is checked and user is admin
if (currentUserIsAdmin && qualityAuditStatus.checked && (!riskFactor.value || riskFactor.value === '')) {
showError('Risk Factor is required when Quality Audit is checked!');
return false;
}
return true;
}
function editAgent(agentId) {
console.log('Edit button clicked for agent:', agentId);
console.log('Current agents array:', agents);

View file

@ -175,7 +175,8 @@
name="quality_audit_status"
value="true"
id="qualityAuditStatus"
{% if not current_user.is_admin %}disabled{% endif %}>
{% if not current_user.is_admin %}disabled{% endif %}
onchange="toggleRiskFactor()">
<label class="form-check-label" for="qualityAuditStatus">
<i class="fas fa-certificate me-2"></i>Quality Audit
{% if current_user.is_admin %}
@ -194,6 +195,32 @@
</div>
</div>
<div class="mb-4" id="riskFactorSection" style="display: none;">
<label for="riskFactor" class="form-label">
<i class="fas fa-exclamation-triangle me-2"></i>Risk Factor
<span class="badge bg-warning text-dark ms-2">Admin Only</span>
<span class="text-danger">*</span>
</label>
<select name="risk_factor"
class="form-select"
id="riskFactor"
{% if not current_user.is_admin %}disabled{% endif %}>
<option value="">Select Risk Level</option>
<option value="1">1 - Very Low Risk</option>
<option value="2">2 - Low Risk</option>
<option value="3">3 - Medium Risk</option>
<option value="4">4 - High Risk</option>
<option value="5">5 - Very High Risk</option>
</select>
<div class="form-text">
{% if current_user.is_admin %}
Required when Quality Audit is checked. Select the appropriate risk level for this agent.
{% else %}
Only administrators can set risk factor levels.
{% endif %}
</div>
</div>
<div class="d-grid mb-4">
<button type="submit" class="btn btn-primary btn-lg">
<i class="fas fa-robot me-2"></i>Register Agent
@ -260,9 +287,26 @@
// Simple form validation and duplicate submission prevention
let formSubmitted = false;
function toggleRiskFactor() {
const qualityAuditStatus = document.getElementById('qualityAuditStatus');
const riskFactorSection = document.getElementById('riskFactorSection');
const riskFactor = document.getElementById('riskFactor');
if (qualityAuditStatus.checked) {
riskFactorSection.style.display = 'block';
riskFactor.required = true;
} else {
riskFactorSection.style.display = 'none';
riskFactor.required = false;
riskFactor.value = '';
}
}
document.getElementById('agentForm').addEventListener('submit', function(e) {
const agentName = document.getElementById('agentName').value.trim();
const agentTool = document.getElementById('agentTool').value.trim();
const qualityAuditStatus = document.getElementById('qualityAuditStatus');
const riskFactor = document.getElementById('riskFactor');
if (!agentName) {
e.preventDefault();
@ -276,6 +320,13 @@ document.getElementById('agentForm').addEventListener('submit', function(e) {
return false;
}
// Validate Risk Factor if Quality Audit is checked
if (qualityAuditStatus.checked && (!riskFactor.value || riskFactor.value === '')) {
e.preventDefault();
alert('Risk Factor is required when Quality Audit is checked!');
return false;
}
// Prevent duplicate submissions
if (formSubmitted) {
e.preventDefault();