Skip to main content
Olatunde Adedeji
  • Home
  • Expertise
  • Case Studies
  • Books
  • Blog
  • Contact

Site footer

AI Applications Architect & Full-Stack Engineering Leader

Designing and delivering production-grade AI systems, grounded retrieval workflows, and cloud-native platforms for teams building real products.

Explore

  • Home
  • Expertise
  • Case Studies

Resources

  • Books
  • Blog
  • Contact

Let's Collaborate

Available for architecture advisory, AI product collaboration, technical writing, and selected consulting engagements.

  • LinkedIn
  • GitHub
  • X (Twitter)
← Blog
AI GovernanceApril 8, 2026·13 min read·By Olatunde Adedeji

Designing RiskRank Copilot: An Explainable AI Prioritization Platform

A practitioner-focused walkthrough of how RiskRank Copilot turns enterprise AI intake, scoring, recommendation logic, and portfolio visibility into a transparent decision-support system.

Designing RiskRank Copilot: An Explainable AI Prioritization Platform

Most organizations do not have an AI idea problem. They have an AI prioritization problem.

That was the real motivation behind RiskRank Copilot. I did not set out to build another AI demo with polished screens and vague promise. I wanted to build a system that helps leadership teams make a harder decision well: which AI opportunities deserve action now, which ones need more groundwork, and which ones should be deferred.

In many companies, AI intake is noisy by default. One business unit frames its proposal around revenue upside. Another talks about efficiency. Another is excited by the novelty of the model itself. Risk, data quality, implementation complexity, and time to value often enter the conversation late, and by then the loudest proposal may already have momentum. The result is not a disciplined portfolio. It is a loosely justified queue.

RiskRank Copilot was designed to make that process more legible. It turns AI prioritization into a structured decision-support workflow: teams submit proposed use cases, the platform scores them across explicit dimensions, recommendation logic interprets the score, and the results surface in a dashboard built for comparison rather than decoration.

Core problem

In practice, AI initiatives rarely fail because nobody can think of one more use case. They fail because organizations struggle to separate ambition from readiness.

A proposal may have strong potential business value but weak data foundations. Another may be technically feasible but too risky for the current governance posture. A third may be attractive in theory but too complex to justify relative to simpler analytics or automation options. Without a shared evaluation model, these trade-offs stay implicit. That makes decisions inconsistent and difficult to explain later.

I wanted RiskRank to address that directly. The platform captures each proposed use case in a structured form, stores both the raw evaluation inputs and the interpreted outputs, and keeps the reasoning visible throughout the workflow.

The underlying model reflects that intent:

python
class UseCase(Base): __tablename__ = "use_cases" title = Column(String(255), nullable=False) business_unit_id = Column(Integer, ForeignKey("business_units.id"), nullable=False) created_by = Column(Integer, ForeignKey("users.id"), nullable=False) problem_statement = Column(Text, nullable=False) business_value = Column(Integer, nullable=False) data_readiness = Column(Integer, nullable=False) feasibility = Column(Integer, nullable=False) complexity = Column(Integer, nullable=False) risk_compliance = Column(Integer, nullable=False) time_to_value = Column(Integer, nullable=False) weighted_score = Column(Float, nullable=True) recommendation = Column(String(100), nullable=True) priority_band = Column(String(20), nullable=True) rationale = Column(Text, nullable=True)

That design choice matters. It means the product does not stop at collection. It turns intake into evaluation.

Design choice

One of the first design decisions I made was to use a weighted scoring model rather than a learned ranking model.

For this kind of enterprise product, explainability mattered more than algorithmic mystique. Leadership teams, product managers, reviewers, and governance stakeholders need to understand why the system says what it says. A transparent scoring approach gives them that. They can inspect the inputs, understand the contribution of each dimension, and see how the final score is formed.

The scoring engine uses six dimensions rated on a one-to-five scale. That is intentionally simple. Users are not asked to think in optimization math. They are asked to assess the opportunity in business language.

jsx
const SCORE_FIELDS = [ { key: 'business_value', label: 'Business Value', help: '1=Low impact, 5=Transformative impact on revenue/cost/experience' }, { key: 'data_readiness', label: 'Data Readiness', help: '1=No data available, 5=Clean, accessible, governed data' }, { key: 'feasibility', label: 'Technical Feasibility', help: '1=Unproven/R&D, 5=Well-established approach with existing tools' }, { key: 'complexity', label: 'Implementation Complexity', help: '1=Simple integration, 5=Multi-system, multi-team effort' }, { key: 'risk_compliance', label: 'Risk & Compliance Sensitivity', help: '1=No regulatory concern, 5=High regulatory/legal/fairness risk' }, { key: 'time_to_value', label: 'Time to Value', help: '1=18+ months, 5=Under 3 months to initial value' }, ];

Two of those dimensions are naturally negative: complexity and risk. If a user enters a high value there, it should reduce readiness rather than improve it. The engine handles that internally by inverting those dimensions during calculation.

python
bv = use_case.business_value dr = use_case.data_readiness fe = use_case.feasibility cx = 6 - use_case.complexity rc = 6 - use_case.risk_compliance tv = use_case.time_to_value

That is a small design detail, but it captures the kind of practitioner thinking I wanted throughout the product: keep the interface intuitive, then handle the nuance in the backend where it belongs.

Score logic

A weighted score is helpful, but on its own it is not enough.

A ranked list tells you relative strength. It does not necessarily tell you what to do next. In real operating environments, that distinction matters. Leaders are not just trying to sort ideas. They are deciding whether to invest in AI now, improve the data layer first, use analytics before more advanced AI, rely on simpler rules-based automation, or defer a proposal because the risk burden outweighs the business case.

The weighted score keeps the model transparent:

python
weighted_score = round( bv * weights.business_value_weight + dr * weights.data_readiness_weight + fe * weights.feasibility_weight + cx * weights.complexity_weight + rc * weights.risk_compliance_weight + tv * weights.time_to_value_weight, 2, )

The default weight profile is deliberately balanced, with business value carrying the most weight while still leaving room for readiness, feasibility, risk, complexity, and time to value to shape the result.

python
business_value_weight = Column(Float, default=0.30) data_readiness_weight = Column(Float, default=0.20) feasibility_weight = Column(Float, default=0.15) complexity_weight = Column(Float, default=0.10) risk_compliance_weight = Column(Float, default=0.15) time_to_value_weight = Column(Float, default=0.10)

That keeps the platform grounded. A big upside story does not get to erase execution reality.

Rule layer

The part that makes RiskRank useful in practice is the recommendation layer on top of the score.

Some conditions deserve special handling. Low data readiness is the clearest example. A proposal can look attractive on business value and feasibility, but if the underlying data is poor, fragmented, or inaccessible, the right recommendation is not to jump into AI build mode. The system should tell you to fix the foundation first.

That logic is explicit:

python
if use_case.data_readiness <= 2: recommendation = "improve_data_first" elif use_case.risk_compliance >= 4 and use_case.business_value <= 3: recommendation = "defer_due_to_risk" elif weighted_score >= 3.8: recommendation = "pursue_ai_now" elif weighted_score >= 3.0: if use_case.feasibility >= 4: recommendation = "use_analytics_first" else: recommendation = "use_rules_automation" else: recommendation = "use_rules_automation"

This is where the platform becomes more than a spreadsheet with formulas. It starts behaving like a governance-aware decision system.

I like this pattern because it mirrors how experienced teams actually reason. They do not blindly follow a composite score. They recognize that some conditions fundamentally change the next step. RiskRank codifies that judgment without hiding it.

Backend role

Architecturally, I wanted the product to stay boring in the best way.

RiskRank uses a straightforward full-stack split. React handles forms, dashboards, detail views, and admin controls. FastAPI handles authentication, use case CRUD, scoring orchestration, and dashboard statistics. The backend persists both the user-entered inputs and the calculated outputs.

That separation matters because prioritization logic should live in one place. When a user submits a use case, the backend immediately applies the active weights, computes the score, assigns the recommendation, derives the priority band, and stores the rationale as part of the record.

python
@router.post("/use-cases", response_model=UseCaseOut) def create_use_case( body: UseCaseCreate, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): uc = UseCase(**body.model_dump(), created_by=current_user.id) weights = get_active_weights(db) result = score_use_case(uc, weights) uc.weighted_score = result["weighted_score"] uc.recommendation = result["recommendation"] uc.priority_band = result["priority_band"] uc.rationale = result["rationale"] uc.status = "submitted" db.add(uc) db.commit()

I prefer that model for products like this because it keeps evaluation consistent across every client and every session. The frontend gathers inputs, but the backend owns judgment.

UI clarity

A transparent decision engine is only useful if the interface makes the decision process readable.

I did not want RiskRank to bury its logic behind novelty visuals or overly dense charts. The job of the dashboard is to support scanning, filtering, and comparison. Leadership should be able to see the portfolio, understand relative ranking, filter by recommendation or business unit, and move quickly from summary to detail.

The dashboard fetches the core portfolio view in one pass:

jsx
const [uc, st, bu] = await Promise.all([ api.getUseCases(filters), api.getStats(), api.getBusinessUnits() ]); setUseCases(uc); setStats(st); setBusinessUnits(bu);

The table itself keeps the comparison legible:

jsx
{useCases.map((uc, i) => ( <tr key={uc.id}> <td><span>{i + 1}</span></td> <td><Link to={`/use-case/${uc.id}`}>{uc.title}</Link></td> <td><strong>{uc.weighted_score?.toFixed(2)}</strong></td> <td>{uc.priority_band}</td> <td>{RECOMMENDATION_LABELS[uc.recommendation] || uc.recommendation}</td> </tr> ))}

The detail page goes one level deeper by showing the score breakdown and rationale in a way stakeholders can actually discuss:

jsx
{SCORE_LABELS.map(({ key, label, inverted }) => ( <div key={key} style={styles.scoreRow}> <span style={styles.scoreLabel}> {label} {inverted && <span>(inverse)</span>} </span> <div style={styles.barContainer}> <div style={{ ...styles.bar, width: `${(uc[key] / 5) * 100}%`, background: inverted ? (uc[key] >= 4 ? '#e74c3c' : '#3498db') : '#3498db' }} /> </div> <span style={styles.scoreVal}>{uc[key]}/5</span> </div> ))} <p>{uc.rationale}</p>

That is not explainability as a buzzword. That is explainability at the UX layer.

Access model

Internal decision-support tools still need clear operational boundaries.

On the frontend, the shared API helper attaches the JWT and centralizes expired-session handling:

javascript
async function request(path, options = {}) { const token = getToken(); const headers = { 'Content-Type': 'application/json', ...options.headers }; if (token) headers['Authorization'] = `Bearer ${token}`; const res = await fetch(`${API_BASE}${path}`, { ...options, headers }); if (res.status === 401) { localStorage.removeItem('riskrank_token'); localStorage.removeItem('riskrank_user'); window.location.href = '/login'; throw new Error('Unauthorized'); } if (!res.ok) { const err = await res.json().catch(() => ({ detail: 'Request failed' })); throw new Error(err.detail || JSON.stringify(err)); } return res.json(); }

On the backend, role checks stay where they belong:

python
def get_current_user( token: str = Depends(oauth2_scheme), db: Session = Depends(get_db) ) -> User: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) user_id_str = payload.get("sub") user = db.query(User).filter(User.id == int(user_id_str)).first() if user is None or not user.is_active: raise credentials_exception return user def require_admin(current_user: User = Depends(get_current_user)) -> User: if current_user.role != "admin": raise HTTPException(status_code=403, detail="Admin access required") return current_user

That pairing gives the platform a clear access model: users authenticate once, the frontend carries the token, and admin-only operations are enforced at the API layer.

Living model

Another important design choice was making the weights configurable through admin controls.

Different organizations do not value the same things in the same way, and even within one organization, priorities change. A regulated environment may temporarily emphasize compliance. A growth push may place more weight on time to value. A team with weak data foundations may want data readiness to matter more until its platform matures.

RiskRank reflects that reality by letting admins adjust the scoring weights, validating that they sum correctly, and re-scoring the full portfolio when changes are saved.

jsx
function total() { if (!weights) return 0; return WEIGHT_FIELDS.reduce((sum, f) => sum + (weights[f.key] || 0), 0); } const t = total(); const validTotal = Math.abs(t - 1.0) <= 0.01; <button onClick={handleSave} disabled={saving || !validTotal}> {saving ? 'Saving...' : 'Save & Re-Score All Use Cases'} </button>

The backend enforces that contract and applies the update consistently:

python
if abs(total - 1.0) > 0.01: raise HTTPException(400, f"Weights must sum to 1.0 (got {total:.2f})") for uc in db.query(UseCase).all(): result = score_use_case(uc, config) uc.weighted_score = result["weighted_score"] uc.recommendation = result["recommendation"] uc.priority_band = result["priority_band"] uc.rationale = result["rationale"] db.commit()

That transforms the system from a fixed model into a living decision framework.

Plain rationale

One piece of the product I especially like is the rationale builder.

Labels such as “Pursue AI Now” or “Improve Data First” are useful, but they are not sufficient on their own. People want to know why the system arrived there. RiskRank assembles rationale text based on the actual characteristics of the use case.

python
rationale_parts = [] if use_case.business_value >= 4: rationale_parts.append("Strong business value justification.") elif use_case.business_value <= 2: rationale_parts.append("Limited business value reduces priority.") if use_case.data_readiness <= 2: rationale_parts.append("Data readiness is insufficient for AI deployment; foundational work needed.") elif use_case.data_readiness >= 4: rationale_parts.append("Data assets are mature and available.") if use_case.risk_compliance >= 4: rationale_parts.append("Elevated compliance/regulatory risk requires governance review.") elif use_case.risk_compliance <= 2: rationale_parts.append("Low regulatory risk profile.")

This may sound small, but it changes the usability of the system. A label tells you the outcome. A rationale gives you the opening for a real discussion.

Test guard

A product like this depends on stable decision logic, so testing matters more than usual.

The scoring engine is heavily tested, and the tests read like executable documentation. They verify that low data readiness overrides otherwise attractive scores, that high risk with weak business value can trigger deferral, and that complexity inversion works in the intended direction.

python
def test_improve_data_first_when_low_data_readiness(self): uc = _make_uc(data_readiness=1, business_value=5, feasibility=5) result = score_use_case(uc, _default_weights()) assert result["recommendation"] == "improve_data_first" def test_defer_due_to_risk(self): uc = _make_uc(risk_compliance=4, business_value=3, data_readiness=4) result = score_use_case(uc, _default_weights()) assert result["recommendation"] == "defer_due_to_risk" def test_data_readiness_takes_priority_over_score(self): uc = _make_uc( business_value=5, data_readiness=1, feasibility=5, complexity=1, risk_compliance=1, time_to_value=5 ) result = score_use_case(uc, _default_weights()) assert result["recommendation"] == "improve_data_first"

That is especially important for systems that shape business judgment. Without tests, decision logic can drift quietly as code evolves. With tests, the core behavioral contract stays visible.

Why it works

What makes RiskRank Copilot interesting is not that it computes a number. Plenty of systems can do that.

What makes it useful is the combination of choices around that number. The product captures enterprise AI opportunities in a structured form. It evaluates them using visible, configurable criteria. It translates the evaluation into recommendation and rationale. Then it presents the portfolio in a UI built for comparison and governance rather than spectacle.

That combination is what turns the project from a scoring exercise into a usable decision-support platform.

It also reflects a broader product principle I care about in AI systems: the important work is often not only in model behavior. It is in how judgment is structured, surfaced, constrained, and made reviewable. In many business settings, that is the difference between an impressive demo and a tool people can actually operate with confidence.

Next steps

If I were extending RiskRank further, I would deepen the portfolio and governance layers.

I would add historical tracking for weight changes and re-scoring events so teams could see how strategy shifts affected prioritization over time. I would add scenario analysis so leaders could ask how the ranking changes under different strategic assumptions. I would likely introduce workflow states for review and approval, along with stronger audit history.

I could also imagine a carefully bounded LLM layer helping generate draft rationales or summarize portfolio trends, while keeping the scoring logic itself deterministic and inspectable. That distinction matters. I would use language models to support interpretation, not to replace the core evaluation contract.

Closing thought

RiskRank Copilot was built around a simple belief: organizations make better AI decisions when the basis for those decisions is explicit.

Not every promising idea should become an AI initiative today. Some need data work. Some need a simpler analytics path. Some are strong enough to pursue immediately. Some should wait. The job of the platform is not to make judgment disappear. The job is to give judgment structure, consistency, and language.

That is what I wanted RiskRank Copilot to do, and that is why explainable prioritization feels like a more useful enterprise AI pattern than yet another black-box ranking story.

AI GovernanceDecision SystemsFastAPIReactExplainable AI
Share
XLinkedInFacebook