The cost of too many dev tools: build a dashboard to visualize unused licenses and overlapping features
costdashboarddevops

The cost of too many dev tools: build a dashboard to visualize unused licenses and overlapping features

UUnknown
2026-02-26
9 min read
Advertisement

Build a small FastAPI + React dashboard to ingest billing and usage CSVs, find unused seats, and surface overlapping tool features for consolidation.

Hook: Why the tools you bought are quietly draining your budget

If your procurement bill looks healthy but engineering productivity feels worse, you probably have too many dev tools. Every unused license, overlapping feature, and duplicated integration adds friction and cost. This guide walks you through building a small, practical web dashboard that ingests billing and usage CSVs, finds unused seats, and surfaces tool overlap so your team can make consolidation decisions backed by data.

What you'll get — fast

In this article you'll get a step‑by‑step blueprint (Python backend + React frontend) to:

  • Ingest billing and usage CSVs from vendors (Stripe, SaaS portals, internal HR/IDP reports).
  • Compute unused seats and estimate savings.
  • Detect tool overlap using feature lists and lightweight semantic matching.
  • Render an actionable dashboard with charts, heatmaps and exportable reports.

Examples are pragmatic and suited for production hardening in 2026: FastAPI (Python), pandas, SQLite/Postgres, and a React (Vite) frontend using Chart libraries. We'll highlight security, data privacy, licensing, and automation hooks (SCIM/Okta/Slack).

Architecture overview

Keep the architecture simple to start; you can iterate to a microservices model later.

  • Frontend: React (Vite) — file upload, charts, tables, export.
  • Backend: FastAPI — CSV parsing, data normalization, analysis endpoints.
  • Storage: SQLite for prototyping; Postgres for production.
  • Data science libs: pandas + scikit‑learn (TF‑IDF) or RapidFuzz for fuzzy matches; possibility to use embeddings (sentence‑transformers) for advanced semantic overlap in 2026.
  • Auth & Security: OAuth2/JWT + HTTPS + encrypted storage.

Prerequisites

  • Python 3.11+ and Node 18+ (2026 LTS).
  • pip, npm/yarn, Docker (optional).
  • Basic familiarity with pandas and React.

Sample CSV schemas

Real vendor exports vary. Standardize on simple canonical schemas early to reduce parsing complexity.

Billing CSV (billing.csv)

vendor,product,plan,licenses_purchased,monthly_cost,currency,account_id
Slack,Slack Enterprise,Standard,120,2400,USD,acct_001
GitHub,GitHub Team,Team,80,1600,USD,acct_002

Usage CSV (usage.csv)

account_id,user_email,last_active_at,active_days_last_90
acct_001,jane@company.com,2026-01-10,75
acct_001,bob@company.com,2025-10-01,0
acct_002,alice@company.com,2026-01-12,80

Step 1 — Backend: project layout & environment

Create a small FastAPI project. Use venv or pipenv and pin dependencies. For prototyping we'll use SQLite and pandas. In production switch to Postgres and managed secrets.

project/
  backend/
    app.py
    models.py
    analyzer.py
    requirements.txt
  frontend/
    (Vite + React app)
  Dockerfile
  docker-compose.yml

Step 2 — CSV ingestion and normalization (Python)

Use pandas to normalize vendor CSVs into canonical tables. Validate columns and cast numeric fields to avoid silent errors.

# analyzer.py (simplified)
import pandas as pd
from datetime import datetime, timedelta

def load_billing(path):
    df = pd.read_csv(path)
    expected = ['vendor','product','plan','licenses_purchased','monthly_cost','currency','account_id']
    assert set(expected).issubset(df.columns), 'billing CSV missing columns'
    df['licenses_purchased'] = df['licenses_purchased'].astype(int)
    df['monthly_cost'] = df['monthly_cost'].astype(float)
    return df

def load_usage(path):
    df = pd.read_csv(path, parse_dates=['last_active_at'])
    expected = ['account_id','user_email','last_active_at','active_days_last_90']
    assert set(expected).issubset(df.columns)
    return df

def compute_active_counts(usage_df, active_threshold_days=30):
    cutoff = datetime.utcnow() - timedelta(days=active_threshold_days)
    active = usage_df[usage_df['last_active_at'] >= cutoff]
    counts = active.groupby('account_id').size().rename('active_user_count').reset_index()
    return counts

Note: pick thresholds (30/90 days) based on team policy. Keep them configurable.

Step 3 — Calculate unused seats and savings

Join billing and usage to compute unused seats and theoretical monthly savings.

def compute_unused_seats(billing_df, usage_df, threshold_days=30):
    active_counts = compute_active_counts(usage_df, active_threshold_days=threshold_days)
    merged = billing_df.merge(active_counts, on='account_id', how='left').fillna(0)
    merged['unused_seats'] = merged['licenses_purchased'] - merged['active_user_count']
    merged['unused_seats'] = merged['unused_seats'].apply(lambda x: max(int(x),0))
    merged['potential_monthly_saving'] = (merged['unused_seats'] / merged['licenses_purchased']).fillna(0) * merged['monthly_cost']
    return merged

Interpretation rules:

  • Only count unused seats if > 5% and > 2 seats to avoid noise.
  • Flag accounts for review before automated actions.

Step 4 — Detecting tool overlap (features + semantic similarity)

A practical approach is to maintain a short feature list per tool and compute pairwise similarity. In 2026 you can optionally use vector embeddings for richer semantics — but TF‑IDF + cosine is lightweight and effective.

# feature_overlap.py
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity

def compute_overlap(tools_features):
    # tools_features: dict {tool_name: 'comma separated features'}
    names = list(tools_features.keys())
    docs = [tools_features[n] for n in names]
    vec = TfidfVectorizer().fit_transform(docs)
    sim = cosine_similarity(vec)
    # return as list of tuples
    pairs = []
    for i in range(len(names)):
        for j in range(i+1, len(names)):
            pairs.append({'a': names[i], 'b': names[j], 'score': float(sim[i,j])})
    return sorted(pairs, key=lambda x: x['score'], reverse=True)

Example tool features map (start small):

tools_features = {
  'Jira': 'issue tracking, workflows, sprints, backlog, kanban',
  'Linear': 'issue tracking, sprints, kanban, integrations',
  'Confluence': 'wiki, docs, knowledge base, pages',
  'Notion': 'docs, wiki, notes, kanban, databases'
}

Highlight pairs with score > 0.45 (tune threshold per data). Use human review to confirm before consolidating.

Step 5 — FastAPI endpoints

Create small endpoints for upload and reports. Protect them with OAuth2 in production.

from fastapi import FastAPI, UploadFile, File
import tempfile
from analyzer import load_billing, load_usage, compute_unused_seats

app = FastAPI()

@app.post('/upload')
async def upload_files(billing: UploadFile = File(...), usage: UploadFile = File(...)):
    with tempfile.NamedTemporaryFile(delete=False) as bfile:
        bfile.write(await billing.read())
    with tempfile.NamedTemporaryFile(delete=False) as ufile:
        ufile.write(await usage.read())
    billing_df = load_billing(bfile.name)
    usage_df = load_usage(ufile.name)
    report = compute_unused_seats(billing_df, usage_df)
    return report.to_dict(orient='records')

Return JSON arrays for the frontend. For large teams stream results or use pagination.

Step 6 — Frontend: React + Vite minimal UI

Create a Vite React app. The minimal UI has:

  1. File upload form for billing.csv and usage.csv
  2. Summary cards (total monthly spend, potential savings)
  3. Table of unused seats and export button
  4. Tool overlap heatmap
// src/App.jsx (simplified)
import React, {useState} from 'react';

export default function App(){
  const [report, setReport] = useState([])
  const [files, setFiles] = useState({})

  const onChange = (e) => setFiles({...files, [e.target.name]: e.target.files[0]})

  const submit = async () => {
    const fd = new FormData();
    fd.append('billing', files.billing)
    fd.append('usage', files.usage)
    const res = await fetch('/upload', {method: 'POST', body: fd})
    const json = await res.json()
    setReport(json)
  }

  const totalSaving = report.reduce((s,r) => s + (r.potential_monthly_saving||0), 0)

  return (
    

License Usage Dashboard

Potential monthly saving: ${totalSaving.toFixed(2)}
{report.map((r,i)=> ( ))}
VendorProductLicensesActive UsersUnused SeatsPotential Saving
{r.vendor}{r.product}{r.licenses_purchased}{r.active_user_count}{r.unused_seats}${r.potential_monthly_saving.toFixed(2)}
) }

For heatmaps and charts, use Recharts or Chart.js. In 2026 consider using React Server Components for faster data flows if integrating with large datasets.

Step 7 — UX and actionability

Make the data actionable — dashboards are only useful if they trigger decisions.

  • Add tags: business critical, team owned, vendor contract end date.
  • Provide recommended actions per row: reclaim seat, move to shared pool, schedule vendor review.
  • Export CSV / PDF for procurement and finance.
  • Integrate with Slack/Teams for approval flows before automated cancellations.

Step 8 — Automation & governance

After human review, automate routine tasks:

  • Use SCIM or vendor APIs to deactivate accounts for unused seats (with audit logs).
  • Integrate with your identity provider (Okta/OneLogin) to reclaim licenses when users are offboarded.
  • Schedule monthly reports and alerts for newly unused licenses.

Tip: don't auto‑cancel subscriptions. Instead, create a ticket or approval workflow to avoid service disruptions.

Security, privacy & license considerations (must‑do in 2026)

CSV files can contain PII. Treat them as sensitive:

  • Encrypt at rest and in transit; use AWS KMS/GCP KMS for keys.
  • Mask or hash emails before storing long‑term.
  • Use strong access controls and audit logs for CSV uploads and reports.
  • Rate limit uploads and validate file types to avoid abuse.

Licensing: check each vendor's contract before programmatically revoking seats or exporting usage to third parties. Keep legal and procurement in the loop.

Tuning thresholds and reducing false positives

Avoid noisy alerts. Apply sensible heuristics:

  • Only display unused seat rows where unused_seats > 5% AND > 2 seats.
  • For paid add‑ons (like extra storage), use financial impact thresholds (e.g., > $100/month).
  • Group small vendors to avoid chasing low impact items.

Late 2025 and early 2026 saw renewed focus on FinOps and AI‑driven procurement. Vendors introduced feature bundles and fine‑grained usage telemetry, enabling better license reclamation. Meanwhile, embeddings and LLMs improved semantic matching for feature overlap detection — but most teams still benefit from lightweight TF‑IDF and rules first.

Consolidation is trending: organizations prefer fewer, deeper integrations over many niche tools — lowering cognitive load and cost.

Practical takeaway: an internal dashboard like this acts as FinOps telemetry for engineering — actionable and low friction.

Testing, deployment & observability

Start with Docker for repeatable builds. Add GitHub Actions for CI that runs linters and unit tests for analysis functions.

# Dockerfile (frontend + backend separate images)
FROM python:3.11-slim
WORKDIR /app
COPY backend/requirements.txt ./
RUN pip install -r requirements.txt
COPY backend/ .
CMD ["uvicorn","app:app","--host","0.0.0.0","--port","8000"]

Observability:

  • Log ingestion events and analysis results.
  • Monitor processing time for large CSVs.
  • Alert on parsing failures or data anomalies.

Advanced strategies (next steps)

  • Use embeddings (OpenAI or open models with sentence‑transformers) to compute semantic overlap for feature descriptions and vendor docs.
  • Apply anomaly detection on usage time series to catch sudden drops in seat activity.
  • Integrate procurement API to schedule renewals and set renewal thresholds.
  • Provide role‑based views: engineering, procurement, finance.

Common pitfalls and how to avoid them

  • Ignoring contract terms — always check auto‑renew windows and minimum term penalties before canceling.
  • Acting on raw counts without human review. Always include a reconciliation step.
  • Overfitting overlap detection — validate with actual team interviews or surveys.

Real‑world example (case study)

One mid‑sized engineering org (200 engineers) used a dashboard like this in early 2026 and found:

  • 15% of Slack seats were unused based on a 60‑day threshold — reclaimed 18 seats and saved $360/month.
  • Detected 3 high‑overlap tool pairs (Notion vs Confluence, Linear vs Jira) and consolidated to reduce tool sprawl.
  • Automated deprovisioning via SCIM for inactive contractors, reducing provisioning errors by 40%.

These actionable insights paid back the engineering time invested in a single quarter.

Wrap up — actionable checklist

  1. Collect billing & usage CSVs; standardize to canonical schema.
  2. Build ingestion + analysis endpoints with FastAPI and pandas.
  3. Compute unused seats with thresholds and surface potential savings.
  4. Maintain a small feature list per tool and compute pairwise overlap (TF‑IDF or embeddings).
  5. Provide reports and human approval workflows before any automated changes.
  6. Harden with encryption, RBAC, and logging; integrate with identity provider.

Call to action

Ready to stop paying for idle tools? Clone the starter repo, run the demo, and adapt thresholds to your policies. If you want, share anonymized CSVs and we’ll help prototype an overlap matrix for your stack. Contribute back with feature lists and playbooks to help other teams reduce tool sprawl in 2026.

Start now: spin up the FastAPI demo, upload billing and usage CSVs, and get a first‑pass savings estimate in under 10 minutes.

Advertisement

Related Topics

#cost#dashboard#devops
U

Unknown

Contributor

Senior editor and content strategist. Writing about technology, design, and the future of digital media. Follow along for deep dives into the industry's moving parts.

Advertisement
2026-02-26T03:47:49.617Z