<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Racharla Gunasekhar</title>
    <description>The latest articles on DEV Community by Racharla Gunasekhar (@racharla_gunasek_2214e8cf).</description>
    <link>https://dev.to/racharla_gunasek_2214e8cf</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4215744%2Fd9164339-b290-42bb-81fb-7846f8e0c8c9.png</url>
      <title>DEV Community: Racharla Gunasekhar</title>
      <link>https://dev.to/racharla_gunasek_2214e8cf</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/racharla_gunasek_2214e8cf"/>
    <language>en</language>
    <item>
      <title>From API Responses to a Useful Dashboard: Connecting JavaScript with FastAPI</title>
      <dc:creator>Racharla Gunasekhar</dc:creator>
      <pubDate>Thu, 29 Oct 2026 17:23:24 +0000</pubDate>
      <link>https://dev.to/racharla_gunasek_2214e8cf/from-api-responses-to-a-useful-dashboard-connecting-javascript-with-fastapi-1p23</link>
      <guid>https://dev.to/racharla_gunasek_2214e8cf/from-api-responses-to-a-useful-dashboard-connecting-javascript-with-fastapi-1p23</guid>
      <description>&lt;p&gt;Introduction&lt;/p&gt;

&lt;p&gt;A backend API can process data correctly, but users still need a simple way to submit information, understand the results, and take action.&lt;/p&gt;

&lt;p&gt;While building my personal Lead Qualification &amp;amp; Scoring Engine, I worked on connecting a browser-based dashboard to a FastAPI backend. The application allows users to enter lead information, submit it for qualification, review scoring results, and revisit previously processed leads.&lt;/p&gt;

&lt;p&gt;In this article, I’ll explain the architecture, frontend-to-backend communication, API response handling, and how structured qualification results can be presented in a useful dashboard.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Understanding the Application Architecture&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The application combines frontend technologies with a Python backend.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTML: Structures the lead form, dashboard, and result sections.&lt;/li&gt;
&lt;li&gt;CSS: Styles the interface, score indicators, and result cards.&lt;/li&gt;
&lt;li&gt;JavaScript: Handles form submissions, API requests, and dynamic updates.&lt;/li&gt;
&lt;li&gt;FastAPI: Processes requests and exposes backend endpoints.&lt;/li&gt;
&lt;li&gt;Pydantic: Validates incoming data.&lt;/li&gt;
&lt;li&gt;SQLAlchemy: Stores and retrieves lead records.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The application follows this general flow:&lt;/p&gt;

&lt;p&gt;User&lt;br&gt;
  |&lt;br&gt;
  v&lt;br&gt;
HTML Form + JavaScript&lt;br&gt;
  |&lt;br&gt;
  | HTTP Request (JSON)&lt;br&gt;
  v&lt;br&gt;
FastAPI Backend&lt;br&gt;
  |&lt;br&gt;
  v&lt;br&gt;
Lead Qualification Services&lt;br&gt;
  |&lt;br&gt;
  v&lt;br&gt;
SQLAlchemy + Database&lt;br&gt;
  |&lt;br&gt;
  v&lt;br&gt;
JSON Response&lt;br&gt;
  |&lt;br&gt;
  v&lt;br&gt;
Dashboard Updates&lt;/p&gt;

&lt;p&gt;The frontend focuses on collecting information and presenting results. The backend handles validation, qualification logic, and persistence.&lt;/p&gt;

&lt;p&gt;Keeping these responsibilities separate makes the application easier to maintain and extend.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Collecting Lead Information&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The workflow begins with a form that collects information about a potential customer.&lt;/p&gt;

&lt;p&gt;Depending on the fields provided, the application can capture details such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name and company&lt;/li&gt;
&lt;li&gt;Job title and industry&lt;/li&gt;
&lt;li&gt;Company size and country&lt;/li&gt;
&lt;li&gt;Pain points and buying signals&lt;/li&gt;
&lt;li&gt;Recent activity and other qualification-related information&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a user submits the form, JavaScript reads the values and prepares a structured request payload.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;const payload = {&lt;br&gt;
  name: nameInput.value.trim(),&lt;br&gt;
  company: companyInput.value.trim(),&lt;br&gt;
  industry: industryInput.value,&lt;br&gt;
  pain_point: painPointInput.value,&lt;br&gt;
  buying_signal: buyingSignalInput.value&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;This is a simplified example showing how form values can be converted into an object. The actual payload should match the fields expected by the backend.&lt;/p&gt;

&lt;p&gt;Preparing a consistent payload helps avoid mismatches between the frontend and API.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sending Requests to FastAPI&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The frontend communicates with the backend through HTTP requests.&lt;/p&gt;

&lt;p&gt;JavaScript’s fetch() API provides a straightforward way to submit JSON data:&lt;/p&gt;

&lt;p&gt;async function submitLead(payload) {&lt;br&gt;
  const response = await fetch("/qualify-lead", {&lt;br&gt;
    method: "POST",&lt;br&gt;
    headers: {&lt;br&gt;
      "Content-Type": "application/json"&lt;br&gt;
    },&lt;br&gt;
    body: JSON.stringify(payload)&lt;br&gt;
  });&lt;br&gt;
  if (!response.ok) {&lt;br&gt;
    throw new Error("Unable to qualify the lead.");&lt;br&gt;
  }&lt;br&gt;
  return await response.json();&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;The request includes three important elements:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The POST method submits the lead information for processing.&lt;/li&gt;
&lt;li&gt;The Content-Type header identifies the request body as JSON.&lt;/li&gt;
&lt;li&gt;JSON.stringify() converts the JavaScript object into a JSON string.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The backend receives the request, validates the data, and executes the qualification workflow.&lt;/p&gt;

&lt;p&gt;Checking response.ok is important because an HTTP request can complete while the server returns an error status. The frontend should handle that case instead of treating every response as successful.&lt;/p&gt;

&lt;p&gt;Note: This code illustrates the request pattern. The endpoint path and payload must match the actual application implementation.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Handling Loading and Error States&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A good dashboard should communicate what is happening when a request takes time.&lt;/p&gt;

&lt;p&gt;For example, an AI-assisted qualification workflow may take longer than a simple database lookup.&lt;/p&gt;

&lt;p&gt;Without feedback, users may submit the form repeatedly because they are unsure whether the first request worked.&lt;/p&gt;

&lt;p&gt;A loading state can improve the experience:&lt;/p&gt;

&lt;p&gt;function setLoading(isLoading) {&lt;br&gt;
  submitButton.disabled = isLoading;&lt;br&gt;
  submitButton.textContent = isLoading&lt;br&gt;
    ? "Analyzing Lead..."&lt;br&gt;
    : "Analyze Lead";&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;The submission handler can enable the loading state before the request and restore the button afterward.&lt;/p&gt;

&lt;p&gt;It should also catch errors and show a clear message when qualification fails.&lt;/p&gt;

&lt;p&gt;Client-side validation is useful for immediate feedback, but the backend must validate the incoming data independently. Browser-side checks alone are not sufficient to protect an API.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Turning API Responses into Useful Insights&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Returning JSON is only one part of building a useful application. Users need to understand the information without reading raw API responses.&lt;/p&gt;

&lt;p&gt;The qualification dashboard presents information such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Overall lead score and classification&lt;/li&gt;
&lt;li&gt;Ideal Customer Profile (ICP) fit&lt;/li&gt;
&lt;li&gt;Scoring breakdown&lt;/li&gt;
&lt;li&gt;Qualification explanation&lt;/li&gt;
&lt;li&gt;Recommended offer and next action&lt;/li&gt;
&lt;li&gt;Sales action plan&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of displaying a large JSON object, the frontend can organize the response into cards, labels, progress bars, and action sections.&lt;/p&gt;

&lt;p&gt;For example, a simplified response might look like this:&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "score": 85,&lt;br&gt;
  "classification": "HOT",&lt;br&gt;
  "icp_fit": true,&lt;br&gt;
  "cached": false&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;This is an illustrative example, not a guaranteed response for a particular lead. The actual response structure depends on the backend schema.&lt;/p&gt;

&lt;p&gt;The frontend should display the score and classification returned by the backend rather than independently calculating them. This keeps the scoring rules in one place and avoids inconsistent results between the UI and server.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Displaying a Scoring Breakdown&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A total score explains how strong a lead appears, but it does not necessarily explain why.&lt;/p&gt;

&lt;p&gt;A scoring breakdown gives users more context by showing how individual criteria contribute to the result.&lt;/p&gt;

&lt;p&gt;For example, a dashboard may use progress bars to display the points awarded for industry relevance, decision-maker status, company size, pain points, buying signals, automation opportunities, and recent activity.&lt;/p&gt;

&lt;p&gt;Each bar can show both the awarded points and the maximum points available for that criterion.&lt;/p&gt;

&lt;p&gt;This improves transparency because users can see which factors contributed to the final score.&lt;/p&gt;

&lt;p&gt;The frontend is responsible for visual presentation, while the backend remains responsible for applying the configured scoring rules and returning the results.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Displaying Cached Results&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The application also supports reusing existing qualification results when the relevant lead information has not changed.&lt;/p&gt;

&lt;p&gt;The backend can indicate whether a result was reused through a field such as cached.&lt;/p&gt;

&lt;p&gt;The frontend can use that information to display an appropriate status message:&lt;/p&gt;

&lt;p&gt;function updateCachedBanner(isCached) {&lt;br&gt;
  cachedBanner.hidden = !isCached;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;When the result is cached, the interface can tell the user that an existing qualification was reused. When a fresh qualification runs, the dashboard can present the new result.&lt;/p&gt;

&lt;p&gt;This small detail connects the backend’s caching behavior to the user experience.&lt;/p&gt;

&lt;p&gt;It also illustrates a broader principle: backend behavior becomes easier to understand when the API communicates useful status information to the frontend.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Managing Previously Qualified Leads&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A dashboard becomes more useful when users can revisit previous work.&lt;/p&gt;

&lt;p&gt;The application stores lead records so users can review previously processed leads instead of entering the same information every time.&lt;/p&gt;

&lt;p&gt;A recent-leads section can display summaries and allow users to open individual records for more details.&lt;/p&gt;

&lt;p&gt;For example, the list might show the lead’s company, score, classification, and last update.&lt;/p&gt;

&lt;p&gt;The backend is responsible for retrieving the saved records, while the frontend presents them in a readable format.&lt;/p&gt;

&lt;p&gt;Separating these responsibilities means the same backend functionality can potentially support other interfaces in the future.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Design Decisions and Lessons Learned&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Building the frontend-to-backend workflow highlighted several important engineering practices.&lt;/p&gt;

&lt;p&gt;Keep request and response structures consistent. The frontend and backend need to agree on field names, data types, and response formats.&lt;/p&gt;

&lt;p&gt;Handle errors explicitly. A useful interface should explain when a request fails instead of leaving the user uncertain.&lt;/p&gt;

&lt;p&gt;Keep business logic in the backend. The UI should display the qualification results rather than independently implementing the scoring rules.&lt;/p&gt;

&lt;p&gt;Make processing status visible. Loading indicators and cached-result messages help users understand what the application is doing.&lt;/p&gt;

&lt;p&gt;Separate presentation from persistence. The frontend handles interaction and display, while the backend manages data validation, qualification, and database operations.&lt;/p&gt;

&lt;p&gt;These practices are useful beyond lead scoring. They apply to dashboards, internal business tools, reporting applications, and other systems that connect a web interface to an API.&lt;/p&gt;

&lt;p&gt;Conclusion&lt;/p&gt;

&lt;p&gt;My personal Lead Qualification &amp;amp; Scoring Engine gave me an opportunity to work across the frontend and backend of a practical application.&lt;/p&gt;

&lt;p&gt;The frontend collects lead information and presents qualification results. FastAPI processes requests, the qualification services evaluate the submitted data, and SQLAlchemy supports persistent lead management.&lt;/p&gt;

&lt;p&gt;The main lesson is that a useful dashboard is not just a collection of UI components. It depends on reliable communication, consistent data structures, clear error handling, and a well-defined separation of responsibilities.&lt;/p&gt;

&lt;p&gt;I am continuing to improve the project and explore how backend APIs can power useful internal tools and workflows.&lt;/p&gt;

&lt;p&gt;This article describes the frontend and backend concepts used in my personal Lead Qualification &amp;amp; Scoring Engine.&lt;/p&gt;

</description>
      <category>python</category>
      <category>fastapi</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Avoiding Duplicate AI Work: SQLAlchemy Persistence and Fingerprint-Based Caching in FastAPI</title>
      <dc:creator>Racharla Gunasekhar</dc:creator>
      <pubDate>Thu, 29 Oct 2026 17:15:07 +0000</pubDate>
      <link>https://dev.to/racharla_gunasek_2214e8cf/avoiding-duplicate-ai-work-sqlalchemy-persistence-and-fingerprint-based-caching-in-fastapi-4dmo</link>
      <guid>https://dev.to/racharla_gunasek_2214e8cf/avoiding-duplicate-ai-work-sqlalchemy-persistence-and-fingerprint-based-caching-in-fastapi-4dmo</guid>
      <description>&lt;p&gt;When building an AI-powered application, sending every request to an AI provider can create unnecessary work. Users may submit the same information more than once, refresh a page, or update a record without changing the data that affects the result.&lt;/p&gt;

&lt;p&gt;During the development of my personal Lead Qualification &amp;amp; Scoring Engine, I worked on this problem by combining SQLAlchemy persistence with a fingerprint-based caching strategy.&lt;/p&gt;

&lt;p&gt;The goal was simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Avoid creating duplicate records for the same lead.&lt;/li&gt;
&lt;li&gt;Reuse an existing qualification when the relevant input has not changed.&lt;/li&gt;
&lt;li&gt;Re-run qualification when meaningful lead information changes.&lt;/li&gt;
&lt;li&gt;Keep the same Lead ID when updating an existing lead.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this article, I’ll explain the design and the implementation behind it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Problem: Repeated Requests&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Imagine a sales representative submits the following lead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name: John Smith&lt;/li&gt;
&lt;li&gt;Company: Example SaaS&lt;/li&gt;
&lt;li&gt;Industry: Software&lt;/li&gt;
&lt;li&gt;Pain point: Manual lead qualification&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The application qualifies the lead and saves the result.&lt;/p&gt;

&lt;p&gt;If the representative submits the same lead again, creating another database record and repeating the AI analysis is unnecessary.&lt;/p&gt;

&lt;p&gt;However, there is an important distinction: if the lead’s buying signal or pain point changes, the application may need to run the qualification process again.&lt;/p&gt;

&lt;p&gt;I needed a way to distinguish between these two situations.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Approach: Identity and Fingerprint&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I used two separate hashes because they answer two different questions.&lt;/p&gt;

&lt;p&gt;Identity key: Is this the same lead?&lt;/p&gt;

&lt;p&gt;The identity key is calculated from the lead’s name and company.&lt;/p&gt;

&lt;p&gt;It helps the application find an existing record for the same person at the same company.&lt;/p&gt;

&lt;p&gt;Fingerprint: Has the qualification input changed?&lt;/p&gt;

&lt;p&gt;The fingerprint is calculated from fields that meaningfully affect qualification, such as the industry, company size, pain point, buying signal, and recent activity.&lt;/p&gt;

&lt;p&gt;The application compares this fingerprint with the one stored in the database.&lt;/p&gt;

&lt;p&gt;This distinction is important: lead identity determines which record to look up, while the fingerprint determines whether the stored qualification can be reused.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Creating a Stable Fingerprint with Python&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I implemented the fingerprint logic using Python’s hashlib and json modules.&lt;/p&gt;

&lt;p&gt;Here is a simplified version of the approach:&lt;/p&gt;

&lt;p&gt;import hashlib&lt;br&gt;
import json&lt;br&gt;
def normalize(value):&lt;br&gt;
    if isinstance(value, str):&lt;br&gt;
        value = value.strip().lower()&lt;br&gt;
        return value or None&lt;br&gt;
    return value&lt;br&gt;
def create_fingerprint(lead_data, fields):&lt;br&gt;
    payload = {&lt;br&gt;
        field: normalize(lead_data.get(field))&lt;br&gt;
        for field in fields&lt;br&gt;
    }&lt;br&gt;
    stable_json = json.dumps(&lt;br&gt;
        payload,&lt;br&gt;
        sort_keys=True,&lt;br&gt;
        ensure_ascii=False&lt;br&gt;
    )&lt;br&gt;
    return hashlib.sha256(&lt;br&gt;
        stable_json.encode("utf-8")&lt;br&gt;
    ).hexdigest()&lt;/p&gt;

&lt;p&gt;This example illustrates the core technique. In my project, the implementation reads the selected fields from the lead object.&lt;/p&gt;

&lt;p&gt;There are three important details here:&lt;/p&gt;

&lt;p&gt;Normalization: Trimming whitespace and normalizing string casing means values such as " Software " and "software" produce the same normalized input.&lt;/p&gt;

&lt;p&gt;Stable serialization: Using sort_keys=True ensures that dictionary key order does not affect the serialized representation.&lt;/p&gt;

&lt;p&gt;SHA-256 hashing: The normalized JSON is converted into a SHA-256 hash. Identical normalized input produces the same fingerprint.&lt;/p&gt;

&lt;p&gt;A hash is useful here as a compact comparison value. It does not replace the original lead data, which is stored separately in the database.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Choosing the Fields Carefully&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Not every field should necessarily invalidate a cached qualification.&lt;/p&gt;

&lt;p&gt;For example, my implementation includes fields such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name and company&lt;/li&gt;
&lt;li&gt;Job title and country&lt;/li&gt;
&lt;li&gt;Industry and company size&lt;/li&gt;
&lt;li&gt;Website and LinkedIn URL&lt;/li&gt;
&lt;li&gt;Pain point and buying signal&lt;/li&gt;
&lt;li&gt;Recent activity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The implementation deliberately excludes custom_offer, which is treated as a display override rather than a qualification input.&lt;/p&gt;

&lt;p&gt;This choice depends on the application’s business rules. If a field starts influencing the qualification result, it should be included in the fingerprint.&lt;/p&gt;

&lt;p&gt;Otherwise, the application could incorrectly reuse a result calculated from older information.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Persisting Lead Data with SQLAlchemy&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I used SQLAlchemy to store lead information and qualification results in a database.&lt;/p&gt;

&lt;p&gt;The lead model includes fields for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The identity key and fingerprint&lt;/li&gt;
&lt;li&gt;Original lead information&lt;/li&gt;
&lt;li&gt;Qualification score and classification&lt;/li&gt;
&lt;li&gt;Scoring breakdown and AI assessment&lt;/li&gt;
&lt;li&gt;Recommended offer and next action&lt;/li&gt;
&lt;li&gt;Sales action plan&lt;/li&gt;
&lt;li&gt;Creation and update timestamps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The model stores structured information in database columns. Some result fields, such as scoring breakdowns and sales action plans, use JSON-compatible columns.&lt;/p&gt;

&lt;p&gt;I also created repository functions to keep database operations separate from the API route logic.&lt;/p&gt;

&lt;p&gt;For example, the update operation modifies an existing SQLAlchemy model instance:&lt;/p&gt;

&lt;p&gt;def update_lead_record(db, record, data):&lt;br&gt;
    for key, value in data.items():&lt;br&gt;
        setattr(record, key, value)&lt;br&gt;
    db.commit()&lt;br&gt;
    db.refresh(record)&lt;br&gt;
    return record&lt;/p&gt;

&lt;p&gt;The important behavior is that this updates the existing record rather than inserting a new one. The existing database ID is therefore preserved.&lt;/p&gt;

&lt;p&gt;In the actual project, the repository functions also handle creating records, retrieving a lead by ID, looking up a lead by identity, and listing recent leads.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Applying the Cache in the FastAPI Endpoint&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The main qualification endpoint follows a straightforward decision process.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Calculate the lead’s identity key.&lt;/li&gt;
&lt;li&gt;Calculate the fingerprint of the qualification-relevant fields.&lt;/li&gt;
&lt;li&gt;Find an existing record using the identity key.&lt;/li&gt;
&lt;li&gt;Compare the stored fingerprint with the newly calculated fingerprint.&lt;/li&gt;
&lt;li&gt;Reuse the stored qualification or run the qualification process, depending on the comparison.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The core decision looks like this:&lt;/p&gt;

&lt;p&gt;existing = get_lead_by_identity(&lt;br&gt;
    db,&lt;br&gt;
    identity_key&lt;br&gt;
)&lt;br&gt;
if existing is not None:&lt;br&gt;
    if existing.fingerprint == fingerprint:&lt;br&gt;
        # Reuse the stored qualification.&lt;br&gt;
        return {&lt;br&gt;
            "cached": True,&lt;br&gt;
            "lead": _lead_dict(existing),&lt;br&gt;
            "qualification": _qualification_dict(existing),&lt;br&gt;
        }&lt;br&gt;
    # The lead exists, but relevant input changed.&lt;br&gt;
    result = analyze_lead(lead)&lt;br&gt;
    # Update the existing record with the new result.&lt;br&gt;
else:&lt;br&gt;
    # New lead: qualify and create a record.&lt;br&gt;
    result = analyze_lead(lead)&lt;/p&gt;

&lt;p&gt;This is a simplified illustration of the decision flow; the complete endpoint also builds the response and persists the new qualification results.&lt;/p&gt;

&lt;p&gt;The actual implementation returns a cached flag and a qualification_source value. This makes it easier to distinguish between a result reused from the database and one generated through a new qualification run.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What Happens in Each Scenario?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Scenario    Application behavior&lt;br&gt;
New person and company  Run qualification and create a record&lt;br&gt;
Same identity and same fingerprint  Reuse the stored qualification&lt;br&gt;
Same identity but changed fingerprint   Re-run qualification and update the existing record&lt;br&gt;
Display-only offer override changes Do not invalidate the fingerprint solely for that change&lt;/p&gt;

&lt;p&gt;This approach keeps the behavior predictable while avoiding unnecessary repeat work.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Engineering Considerations&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Implementing a fingerprint-based cache requires more than generating a hash.&lt;/p&gt;

&lt;p&gt;Keep the fingerprint definition aligned with business logic&lt;/p&gt;

&lt;p&gt;If qualification starts depending on a new field, update the fingerprint field list as well. Otherwise, stale results may be reused.&lt;/p&gt;

&lt;p&gt;Handle normalization deliberately&lt;/p&gt;

&lt;p&gt;Normalization should reflect the meaning of the data. Lowercasing text is reasonable for many names and descriptive fields, but it should not be applied indiscriminately to every type of value.&lt;/p&gt;

&lt;p&gt;Consider changes to the scoring rules&lt;/p&gt;

&lt;p&gt;The fingerprint in this implementation represents the lead input. If scoring rules or model behavior change, the same input may produce a different result.&lt;/p&gt;

&lt;p&gt;For a production system, consider including a scoring-rule version or qualification version in the cache key, or invalidating cached results when those dependencies change.&lt;/p&gt;

&lt;p&gt;Think about concurrent requests&lt;/p&gt;

&lt;p&gt;Two requests for the same new lead can arrive at nearly the same time. A production implementation should consider database uniqueness constraints, transaction handling, and concurrency so that simultaneous requests do not create duplicate records.&lt;/p&gt;

&lt;p&gt;Measure the benefit&lt;/p&gt;

&lt;p&gt;Caching can reduce repeated AI calls, but its actual impact depends on how frequently duplicate requests occur. Logging cache hits, cache misses, and qualification duration can help evaluate whether the approach is effective.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What I Learned&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This implementation helped me understand that caching is not simply storing a response and returning it later.&lt;/p&gt;

&lt;p&gt;The difficult part is deciding when a stored result is still valid.&lt;/p&gt;

&lt;p&gt;Separating lead identity from qualification fingerprints made the behavior easier to reason about. SQLAlchemy persistence gave the application a consistent place to store both the input data and its qualification results, while FastAPI provided the endpoint through which the workflow runs.&lt;/p&gt;

&lt;p&gt;The same ideas can be useful in other applications that process repeated inputs, including document analysis, product recommendations, reporting tools, and AI-assisted workflows.&lt;/p&gt;

&lt;p&gt;Conclusion&lt;/p&gt;

&lt;p&gt;Building the Lead Qualification &amp;amp; Scoring Engine gave me an opportunity to apply hashing, database persistence, and caching to a practical backend problem.&lt;/p&gt;

&lt;p&gt;The main lesson is that a useful cache needs a clear definition of identity and a reliable way to determine whether the underlying input has changed.&lt;/p&gt;

&lt;p&gt;By combining a stable fingerprint with SQLAlchemy persistence, the application can reuse unchanged qualification results and update existing lead records when relevant information changes.&lt;/p&gt;

&lt;p&gt;I’m continuing to improve the project and explore ways to make AI-powered applications more reliable, explainable, and efficient.&lt;/p&gt;

&lt;p&gt;This article describes the implementation in my personal Lead Qualification &amp;amp; Scoring Engine&lt;/p&gt;

</description>
      <category>python</category>
      <category>fastapi</category>
      <category>sqlalchemy</category>
      <category>backend</category>
    </item>
    <item>
      <title>Building an Explainable AI-Powered Lead Scoring Engine with FastAPI</title>
      <dc:creator>Racharla Gunasekhar</dc:creator>
      <pubDate>Thu, 29 Oct 2026 17:05:52 +0000</pubDate>
      <link>https://dev.to/racharla_gunasek_2214e8cf/building-an-explainable-ai-powered-lead-scoring-engine-with-fastapi-1m67</link>
      <guid>https://dev.to/racharla_gunasek_2214e8cf/building-an-explainable-ai-powered-lead-scoring-engine-with-fastapi-1m67</guid>
      <description>&lt;p&gt;Building an Explainable AI-Powered Lead Scoring Engine with FastAPI&lt;/p&gt;

&lt;p&gt;Introduction&lt;/p&gt;

&lt;p&gt;Sales teams often receive many leads, but not every prospect is equally likely to become a customer. Manually reviewing every lead takes time and makes it difficult to identify the most promising opportunities.&lt;/p&gt;

&lt;p&gt;I built a personal project called the Lead Qualification and Scoring Engine to explore how AI and rule-based business logic can work together to solve this problem.&lt;/p&gt;

&lt;p&gt;The application evaluates lead information, checks Ideal Customer Profile (ICP) fit, calculates a score from 0 to 100, and generates a lead classification and recommended next action.&lt;/p&gt;

&lt;p&gt;In this article, I’ll explain the architecture, scoring approach, API design, and engineering lessons behind the project.&lt;/p&gt;

&lt;p&gt;Technology stack&lt;/p&gt;

&lt;p&gt;The project uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python for business logic.&lt;/li&gt;
&lt;li&gt;FastAPI for REST API endpoints.&lt;/li&gt;
&lt;li&gt;Pydantic for request and response validation.&lt;/li&gt;
&lt;li&gt;SQLAlchemy for database operations.&lt;/li&gt;
&lt;li&gt;SQLite for data persistence.&lt;/li&gt;
&lt;li&gt;AI integration for interpreting lead information.&lt;/li&gt;
&lt;li&gt;HTML, CSS, and JavaScript for the frontend.&lt;/li&gt;
&lt;li&gt;Pytest for automated testing.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Understanding the architecture&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The application follows a sequence of steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Receive lead information.&lt;/li&gt;
&lt;li&gt;Check for an existing lead and reusable results.&lt;/li&gt;
&lt;li&gt;Gather available company research.&lt;/li&gt;
&lt;li&gt;Use AI to interpret relevant business signals.&lt;/li&gt;
&lt;li&gt;Evaluate ICP fit using configured rules.&lt;/li&gt;
&lt;li&gt;Calculate a deterministic score in Python.&lt;/li&gt;
&lt;li&gt;Generate the classification and recommendations.&lt;/li&gt;
&lt;li&gt;Store the results and return a structured response.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One important design decision is separating AI interpretation from numerical scoring.&lt;/p&gt;

&lt;p&gt;AI can help interpret unstructured information, while deterministic application code applies the configured business rules. This makes the final score easier to explain and test.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Implementing configurable lead scoring&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A scoring engine should not be a mysterious black box. Sales teams need to understand why a lead received a particular score.&lt;/p&gt;

&lt;p&gt;My project stores scoring configuration in a JSON rules file. The configured criteria cover industry relevance, decision-maker relevance, company size, pain points, buying signals, automation opportunities, and recent activity.&lt;/p&gt;

&lt;p&gt;Each criterion contributes its configured weight when the relevant condition is satisfied.&lt;/p&gt;

&lt;p&gt;For example, consider a hypothetical lead:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftxbj052elvlfqtm5h7hc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftxbj052elvlfqtm5h7hc.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
Criterion   Points&lt;br&gt;
Target industry 20&lt;br&gt;
Decision-maker relevance    15&lt;br&gt;
Company size    10&lt;br&gt;
Clear pain point    20&lt;br&gt;
Buying signal   0&lt;br&gt;
Automation opportunity  10&lt;br&gt;
Recent activity 5&lt;br&gt;
Total   80&lt;/p&gt;

&lt;p&gt;This example demonstrates how a score can be explained through individual criteria rather than returned as a single unexplained number.&lt;/p&gt;

&lt;p&gt;The actual scoring rules and thresholds should remain consistent with the application’s configuration.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Why use rules alongside AI?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;AI is useful for interpreting descriptions that do not follow a fixed format. For example, a prospect might describe manual workflows or operational challenges without explicitly labelling them as an automation opportunity.&lt;/p&gt;

&lt;p&gt;However, asking an AI model to invent a final score can make results difficult to reproduce.&lt;/p&gt;

&lt;p&gt;A hybrid approach separates responsibilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI interprets available information.&lt;/li&gt;
&lt;li&gt;Business rules evaluate qualification requirements.&lt;/li&gt;
&lt;li&gt;Python calculates the score.&lt;/li&gt;
&lt;li&gt;The API returns the result in a structured format.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This separation makes it easier to test the scoring logic independently of the AI provider.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Building REST APIs with FastAPI&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;FastAPI provides endpoints that allow the frontend and other applications to interact with the qualification engine.&lt;/p&gt;

&lt;p&gt;The project includes operations for qualifying leads, retrieving stored leads, viewing an individual lead, and checking service health.&lt;/p&gt;

&lt;p&gt;Pydantic schemas help validate incoming request data before it reaches the business logic.&lt;/p&gt;

&lt;p&gt;A structured API response can expose the score, classification, qualification status, scoring breakdown, matched criteria, and recommended next action.&lt;/p&gt;

&lt;p&gt;This structure also makes the engine suitable for integration with a CRM or an internal business dashboard.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Storing results with SQLAlchemy and SQLite&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The application uses SQLAlchemy models and SQLite to persist lead information and qualification results.&lt;/p&gt;

&lt;p&gt;Persistence allows the system to retrieve previous results without depending on temporary in-memory data.&lt;/p&gt;

&lt;p&gt;Keeping database operations in a separate repository layer also helps separate data access from scoring and analysis.&lt;/p&gt;

&lt;p&gt;SQLite is convenient for local development because it requires minimal setup. SQLAlchemy provides a familiar abstraction for working with relational data.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reducing repeated work with fingerprint-based caching&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Repeatedly analyzing unchanged lead information can increase processing time and AI API usage.&lt;/p&gt;

&lt;p&gt;The project uses a lead identity and a fingerprint of qualification-relevant information to determine whether an existing result can be reused.&lt;/p&gt;

&lt;p&gt;When the same lead is submitted with unchanged relevant information, the application can reuse its stored result. When relevant information changes, the qualification process can run again.&lt;/p&gt;

&lt;p&gt;This approach helps avoid unnecessary work while preserving the ability to refresh results when the underlying information changes.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Testing the application&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Automated tests are important because a correct HTTP response does not necessarily mean the qualification result is correct.&lt;/p&gt;

&lt;p&gt;The project includes tests for areas such as scoring, API behaviour, ICP validation, AI services, research, sales action plans, and fingerprint-based caching.&lt;/p&gt;

&lt;p&gt;Useful test cases include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verifying the expected score for known assessment inputs.&lt;/li&gt;
&lt;li&gt;Checking that an ICP mismatch is handled correctly.&lt;/li&gt;
&lt;li&gt;Confirming that invalid requests are rejected.&lt;/li&gt;
&lt;li&gt;Ensuring unchanged lead data can reuse an existing result.&lt;/li&gt;
&lt;li&gt;Checking how the application handles failures in external services.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Deterministic business logic is particularly valuable because it can be tested without depending on an AI model producing exactly the same interpretation every time.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Engineering lessons&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Building this project reinforced several useful principles.&lt;/p&gt;

&lt;p&gt;Make business rules explicit. Configurable scoring criteria make changes easier to understand and maintain.&lt;/p&gt;

&lt;p&gt;Separate AI from deterministic decisions. AI interpretation and numerical calculations have different responsibilities.&lt;/p&gt;

&lt;p&gt;Design for failure. External services can become unavailable or return unusable output, so fallback behaviour needs to be tested.&lt;/p&gt;

&lt;p&gt;Avoid unnecessary processing. Reusing stored results can reduce repeated analysis for unchanged information.&lt;/p&gt;

&lt;p&gt;Test the business logic. Tests should validate qualification outcomes, not just successful API responses.&lt;/p&gt;

&lt;p&gt;Conclusion&lt;/p&gt;

&lt;p&gt;The Lead Qualification and Scoring Engine combines AI-assisted interpretation, configurable business rules, REST APIs, database persistence, and automated testing.&lt;/p&gt;

&lt;p&gt;The goal is not simply to assign a number to a prospect. It is to produce an understandable qualification result that other applications can consume.&lt;/p&gt;

&lt;p&gt;This project gave me practical experience with API development, AI integration, data persistence, caching, and testable business logic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffe5fal8d0lyf2btoblfs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffe5fal8d0lyf2btoblfs.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
Future improvements could include stronger monitoring, more extensive evaluation of AI-generated assessments, and integration with an internal dashboard for reviewing and managing qualified leads.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/..." class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/..." alt="Uploading image" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Technologies: Python, FastAPI, Pydantic, SQLAlchemy, SQLite, AI integration, JavaScript, HTML, CSS, and Pytest.&lt;/p&gt;

</description>
      <category>python</category>
      <category>fastapi</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
