Supplier Monitoring with 483 Intelligence
Use CTWise 483 Intelligence to monitor your suppliers' FDA inspection compliance, assess facility risk, and build data-driven supplier qualification workflows.
Introduction
Why Monitor Suppliers for FDA Inspections?
FDA 483 observations can signal serious compliance issues that may affect:
- Product Quality: OAI findings often indicate systemic quality problems
- Supply Continuity: Enforcement actions can disrupt manufacturing
- Regulatory Risk: Your products may be affected by supplier deficiencies
- Audit Findings: Regulators expect you to monitor supplier compliance
The Cost of Missed 483s
Missing a critical supplier 483 can result in:
- Product Recalls: $1M-$10M+ in direct costs
- Supply Chain Disruption: 6-18 months to qualify alternate suppliers
- Regulatory Citations: FDA may cite you for inadequate supplier oversight (21 CFR 211.84)
- Delayed Approvals: New applications may be delayed if supplier has OAI status
Step-by-Step: Setting Up Supplier Monitoring
Step 1: Identify Your Suppliers' FEI Numbers
Every FDA-registered facility has a unique Facility Establishment Identifier (FEI) number. You need FEI numbers to query the 483 Intelligence API.
Where to find FEI numbers:
- Quality Agreements (Section 1.2 - Facility Information)
- Supplier Qualification Packages
- FDA Data Dashboard: datadashboard.fda.gov
You can also search for facilities using the CTWise API:
import os
import requests
API_KEY = os.getenv("CTWISE_API_KEY")
BASE_URL = "https://api.ctwise.ai/v1"
# Search facilities by state
response = requests.get(
f"{BASE_URL}/483/facilities",
headers={"X-Api-Key": API_KEY},
params={"state": "NJ", "limit": 20}
)
for facility in response.json()["results"]:
print(f"{facility['fei_number']}: {facility['legal_name']} ({facility['city']}, {facility['state']})")
Step 2: Get Facility Profiles
Once you have FEI numbers, retrieve detailed facility profiles:
# Your critical suppliers
supplier_feis = [
"1000116488", # Supplier A
"3002809064", # Supplier B
"1000234567", # Supplier C
]
for fei in supplier_feis:
response = requests.get(
f"{BASE_URL}/483/facilities/{fei}",
headers={"X-Api-Key": API_KEY}
)
if response.status_code == 200:
facility = response.json()
print(f"\n{'='*50}")
print(f"Facility: {facility['legal_name']}")
print(f"FEI: {facility['fei_number']}")
print(f"Location: {facility['city']}, {facility['state']}")
print(f"Total Inspections: {facility['total_inspections']}")
print(f"Classifications: OAI={facility['classification_breakdown']['oai_count']}, "
f"VAI={facility['classification_breakdown']['vai_count']}, "
f"NAI={facility['classification_breakdown']['nai_count']}")
print(f"Risk: {facility['risk_assessment']['risk_score']:.1f} "
f"({facility['risk_assessment']['risk_level']})")
Step 3: Check Risk Scores
Get detailed risk scores with 5-factor breakdowns (v2.0 methodology):
for fei in supplier_feis:
response = requests.get(
f"{BASE_URL}/483/risk-scores/{fei}",
headers={"X-Api-Key": API_KEY}
)
if response.status_code == 200:
risk = response.json()
print(f"\nFEI: {risk['fei_number']}")
print(f" Risk Score: {risk['risk_score']:.1f}/100 ({risk['risk_level']})")
print(f" Methodology: {risk['methodology_version']}")
# Show factor breakdown
print(f" Factor Breakdown:")
max_vals = {"oai_ratio": 40, "citation_frequency": 25, "recency": 15, "severity": 10, "peer_benchmark": 10}
for name, value in risk["factors"].items():
print(f" {name}: {value:.1f}/{max_vals[name]}")
Step 4: Review Citation History
Get the citation history for specific facilities:
fei = "1000116488"
response = requests.get(
f"{BASE_URL}/483/facilities/{fei}/citations",
headers={"X-Api-Key": API_KEY},
params={"limit": 50}
)
data = response.json()
print(f"Total citations: {data['total']}\n")
# Group by CFR
from collections import Counter
cfr_counts = Counter(obs["act_cfr_number"] for obs in data["results"])
print("Most cited CFRs:")
for cfr, count in cfr_counts.most_common(5):
print(f" {cfr}: {count} citations")
Step 5: Search for Specific Compliance Concerns
Use semantic search to find relevant observations for your suppliers:
# Search for data integrity issues
response = requests.post(
f"{BASE_URL}/483/observations/search",
headers={
"X-Api-Key": API_KEY,
"Content-Type": "application/json"
},
json={
"query": "data integrity electronic records audit trail failures",
"top_k": 10,
"min_similarity": 0.70
}
)
results = response.json()
for obs in results["results"]:
# Check if any of your suppliers appear
if obs["fei_number"] in supplier_feis:
print(f"ALERT: {obs['legal_name']} ({obs['fei_number']})")
print(f" CFR: {obs['act_cfr_number']}")
print(f" {obs['short_description'][:150]}...")
print(f" Similarity: {obs['similarity_score']:.2f}")
print()
Building a Supplier Risk Dashboard
Combine all API endpoints to create a comprehensive supplier monitoring report:
#!/usr/bin/env python3
"""
Supplier Risk Dashboard
Generates a comprehensive supplier compliance report using CTWise 483 Intelligence.
"""
import os
import requests
import pandas as pd
from datetime import datetime
API_KEY = os.getenv("CTWISE_API_KEY")
BASE_URL = "https://api.ctwise.ai/v1"
def get_supplier_risk_data(fei_list):
"""Fetch risk scores and facility data for a list of suppliers."""
results = []
for fei in fei_list:
# Get risk score
risk_resp = requests.get(
f"{BASE_URL}/483/risk-scores/{fei}",
headers={"X-Api-Key": API_KEY}
)
if risk_resp.status_code != 200:
continue
risk = risk_resp.json()
factors = risk["factors"]
# Also get facility profile for additional context
fac_resp = requests.get(
f"{BASE_URL}/483/facilities/{fei}",
headers={"X-Api-Key": API_KEY}
)
fac = fac_resp.json() if fac_resp.status_code == 200 else {}
results.append({
"fei_number": risk["fei_number"],
"legal_name": fac.get("legal_name", fei),
"risk_score": risk["risk_score"],
"risk_level": risk["risk_level"],
"oai_ratio": factors["oai_ratio"],
"citation_freq": factors["citation_frequency"],
"peer_benchmark": factors["peer_benchmark"],
"total_inspections": fac.get("total_inspections", 0),
"last_inspection": fac.get("last_inspection_date", "N/A"),
})
return pd.DataFrame(results)
def generate_dashboard(fei_list):
"""Generate supplier risk dashboard."""
df = get_supplier_risk_data(fei_list)
if df.empty:
print("No supplier data available.")
return
df = df.sort_values("risk_score", ascending=False)
print("\n" + "="*70)
print("SUPPLIER COMPLIANCE RISK DASHBOARD")
print(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M')}")
print("="*70)
# Summary
print(f"\nTotal Suppliers: {len(df)}")
print(f"Average Risk Score: {df['risk_score'].mean():.1f}")
print(f"\nRisk Distribution:")
for level in ["critical", "high", "medium", "low"]:
count = len(df[df["risk_level"] == level])
if count > 0:
print(f" {level}: {count} suppliers")
# Detail table
print(f"\n{'='*70}")
print(f"{'Supplier':<30} {'Risk':>6} {'Level':<10} {'OAI Ratio':>10} {'Last Inspection':<16}")
print(f"{'-'*30} {'-'*6} {'-'*10} {'-'*10} {'-'*16}")
for _, row in df.iterrows():
name = row["legal_name"][:28]
print(f"{name:<30} {row['risk_score']:>5.1f} {row['risk_level']:<10} "
f"{row['oai_ratio']:>9.1f} {row['last_inspection']:<16}")
# High-risk alerts
high_risk = df[df["risk_level"].isin(["high", "critical"])]
if not high_risk.empty:
print(f"\nACTION REQUIRED: {len(high_risk)} high/critical risk suppliers")
for _, row in high_risk.iterrows():
print(f" - {row['legal_name']}: score {row['risk_score']:.1f} "
f"({row['risk_level']})")
print("\n" + "="*70)
# Export to CSV
csv_path = f"supplier_risk_{datetime.now().strftime('%Y%m%d')}.csv"
df.to_csv(csv_path, index=False)
print(f"Exported to: {csv_path}")
return df
if __name__ == "__main__":
MY_SUPPLIERS = [
"1000116488", # Supplier A
"3002809064", # Supplier B
"1000234567", # Supplier C
"1000345678", # Supplier D
]
generate_dashboard(MY_SUPPLIERS)
Supplier Qualification Workflow
Use risk scores and citation history to build a data-driven supplier qualification process:
def qualify_supplier(fei, supplier_name):
"""Evaluate supplier FDA compliance for qualification decision."""
# Step 1: Get facility profile
facility = requests.get(
f"{BASE_URL}/483/facilities/{fei}",
headers={"X-Api-Key": API_KEY}
).json()
# Step 2: Get risk score (v2.0 with 5-factor breakdown)
risk = requests.get(
f"{BASE_URL}/483/risk-scores/{fei}",
headers={"X-Api-Key": API_KEY}
).json()
# Step 3: Get citation history
citations = requests.get(
f"{BASE_URL}/483/facilities/{fei}/citations",
headers={"X-Api-Key": API_KEY},
params={"limit": 100}
).json()
# Decision logic using risk score and factors
risk_score = risk["risk_score"]
risk_level = risk["risk_level"]
factors = risk["factors"]
if risk_level == "critical":
decision = "REJECT"
reason = "Critical risk level"
elif risk_level == "high" or factors["oai_ratio"] > 25:
decision = "CONDITIONAL"
reason = f"High risk ({risk_score:.1f}) or high OAI ratio ({factors['oai_ratio']:.1f}/40)"
elif risk_level == "medium":
decision = "APPROVE WITH MONITORING"
reason = f"Medium risk ({risk_score:.1f}), enhanced monitoring recommended"
else:
decision = "APPROVE"
reason = f"Low risk ({risk_score:.1f}), standard monitoring"
print(f"\n{'='*60}")
print(f"SUPPLIER QUALIFICATION: {supplier_name}")
print(f"{'='*60}")
print(f"FEI: {fei}")
print(f"Facility: {facility['legal_name']}")
print(f"Location: {facility['city']}, {facility['state']}")
print(f"Risk Score: {risk_score:.1f}/100 ({risk_level})")
print(f"Methodology: {risk['methodology_version']}")
print(f"Top Factor: oai_ratio={factors['oai_ratio']:.1f}/40")
print(f"Citations: {citations['total']}")
print(f"\nDECISION: {decision}")
print(f"Reason: {reason}")
print(f"{'='*60}")
return {"decision": decision, "risk_score": risk_score, "reason": reason}
Best Practices
Review Frequency Recommendations
| Supplier Criticality | Review Frequency | What to Check |
|---|---|---|
| High (Single source, API, sterile) | Monthly | Risk scores + recent citations |
| Medium (Alternate source available) | Quarterly | Risk scores |
| Low (Non-critical, easy substitution) | Semi-annually | Risk scores |
Escalation Procedures
Level 1 - Low Risk (risk_level: low)
- Document in supplier file
- Standard annual audit schedule
Level 2 - Medium Risk (risk_level: medium)
- Review within 5 business days
- Contact supplier for status update
- QA Manager review
Level 3 - High Risk (risk_level: high)
- Review within 2 business days
- Request supplier CAPA plan
- Conduct supplier audit
- Executive notification
Level 4 - Critical Risk (risk_level: critical)
- Review within 24 hours
- Immediate supplier meeting
- Assess impact on products
- Evaluate alternate sourcing
- VP Quality escalation
Recommended Workflow SOP
-
Periodic Check (per frequency above)
- Query risk scores for all monitored suppliers
- Compare against previous scores
-
Risk Increase Detected
- Review citation history for new findings
- Identify which CFR areas are affected
- Assess relevance to your products
-
Supplier Contact (if risk increased)
- Request explanation for new findings
- Request CAPA plan if OAI
- Schedule follow-up
-
Documentation
- Record risk scores in supplier file
- Document assessment and decisions
- Update supplier qualification status
Next Steps
- Gather FEI numbers for your critical suppliers
- Run initial risk assessments using the dashboard script
- Establish review cadence based on supplier criticality
- Build qualification criteria using risk scores as one input
For more information: