I Scanned Hundreds of AI-Generated Codebases. Here’s What Keeps Showing Up.

I Scanned Hundreds of AI-Generated Codebases. Here’s What Keeps Showing Up.

There’s a moment every vibe coder knows.

You’ve been building for six hours straight. Cursor has been finishing your sentences. The app actually works , you can click through it, the data saves, the auth flow runs. You push to production feeling like you’ve unlocked a cheat code for software development.

Then a user emails you.

Or worse , they don’t. They just leave.

I’ve spent the last several months doing something most developers skip: scanning AI-generated codebases before they ship. Not after. Before. AI analysis followed by human engineers who know exactly what to look for. And the same vulnerabilities keep appearing not occasionally, not in bad codebases, but constantly, across projects built by smart people using the best tools available.

This isn’t a piece about AI being bad. I use AI tools every day. This is about a specific, predictable blind spot one that’s costing founders users, trust, and in some cases, their entire product.

Let me show you what I keep finding.

The Problem Isn’t That AI Writes Bad Code

That’s the misconception worth killing first.

AI coding tools Cursor, Bolt, v0, GitHub Copilot, Claude write remarkably functional code. That’s exactly what makes this dangerous. The code compiles. The tests pass if you wrote any. The happy path works beautifully.

But Veracode found that 45% of AI-generated code introduces security vulnerabilities. CodeRabbit found that AI co-authored projects have 2.74× more security issues than human-written code. Forrester projects $1.5 trillion in technical debt by 2027 from AI-generated code alone.

These aren’t fringe cases. These are the statistical outcomes of a tool optimized for plausible, functional output not defensive, adversarial-resistant output.

The distinction matters. A lot.

What I Actually Keep Finding

1. SQL Injection The Classic That AI Keeps Reinventing

I see this in probably 60% of codebases with a database layer. Here’s what AI generates:

const user = await db.query(
  `SELECT * FROM users WHERE email = '${req.body.email}'`
);

It works perfectly until someone sends ' OR '1'='1 as their email address and reads your entire users table. That string interpolation isn’t a style choice. It’s an open door.

The parameterized version isn’t even harder to write:

const user = await db.query(
  'SELECT * FROM users WHERE email = $1',
  [req.body.email]
);

AI knows this pattern exists. It just doesn’t consistently choose it unless you specifically ask and most people don’t know to ask.

2. Secrets in Source Code

This one is quiet and catastrophic.

const stripe = require('stripe')('sk_live_424242...');
const db = new Client({ connectionString: 'postgresql://admin:prod-password@...' });

AI completes code based on context and comments. When your comment says // Connect to Stripe and your variable is named stripeKey, it fills in something that looks right. Sometimes it pulls from patterns in your own codebase including things you typed once and deleted.

The secret is now in your source. If it reaches your git history, it’s effectively permanent. Secret scanners run on public repos constantly. This is how API keys get harvested within hours of a push.

The .env pattern isn’t complicated. It’s just not what AI defaults to.

3. Unprotected Admin Routes

app.get('/admin/users', async (req, res) => {
  const users = await db.query('SELECT * FROM users');
  res.json(users);
});

AI generates the route. It works. You test it. You move on.

What’s missing: any check that the person hitting /admin/users is actually an admin. Or even logged in. It’s not that AI doesn’t know about auth middleware it’s that you didn’t ask for a protected admin route, so it gave you a route that works.

I’ve seen full user databases exposed this way. Real email addresses. Real data. In apps that had been live for months.

4. File Upload Path Traversal

This one is subtle enough that even experienced developers miss it:

filename = file.filename
file.save(f'./uploads/{filename}')

Looks reasonable. Works in testing. But what if filename is ../../etc/passwd? Or ../app.py? The AI gave you the functionality you asked for. It didn’t model what an attacker would do with it.

5. Missing Input Validation Everywhere

app.post('/transfer', async (req, res) => {
  const { amount, toAccount } = req.body;
  await transfer(amount, toAccount);
  res.json({ success: true });
});

What happens when amount is -10000? Or null? Or a string? Or 999999999999?

AI generates the optimistic path the code that works when users do what you expect. It doesn’t naturally generate the pessimistic path the code that survives what users actually do.

Why This Keeps Happening (And Why Being More Careful Isn’t the Answer)

Here’s the structural reality: LLMs are trained to predict what code should look like, not to think like an attacker.

Security thinking is fundamentally adversarial. It requires asking: what could go wrong here? what would someone malicious do with this input? what happens at the edges? These aren’t questions an autocomplete model naturally asks. They’re questions that come from experience watching things break in production.

The instinct most developers have “I’ll just be more careful” doesn’t scale. Not when you’re shipping fast. Not when you’re using an AI tool that generates 200 lines in the time it takes you to review 20.

You need a different layer in your process.

What That Layer Looks Like

After scanning enough codebases to see these patterns become statistical certainties, I built Assayer to do this properly not just with AI, but with the combination that actually works.

Here’s how it works:

You connect your GitHub repo. The AI scanner maps your codebase , your stack, your patterns, your actual vulnerabilities and flags every finding with the exact file and line number. Then a human senior engineer reviews what the AI found.

That last part matters. AI catches things fast. Humans understand context. Together they catch what either alone would miss.

You get two paths depending on what you need:

Path A — Review: Full report with exact fix instructions. You implement them.

Path B — Review + Fix: The engineers do the work directly. You ship.

It’s not a linter. It’s not a generic SAST tool. It’s the layer between your AI-built codebase and your real users , staffed by people who think adversarially about code for a living.

The scan is free. Connect your repo here →

(Beta ends April 5 — after that, scans move to a credit system.)

The Checklist (For Right Now, Before You Read Further)

If you have something shipping soon, run through this before you push:

  • [ ] All database queries use parameterized inputs — no string interpolation
  • [ ] Passwords are hashed with bcrypt or argon2 — never compared in plain text
  • [ ] Secrets live in .env files — never hardcoded in source
  • [ ] Every admin route checks authentication AND authorization
  • [ ] File uploads sanitize filenames and validate file types
  • [ ] Every external input is validated before it touches your database
  • [ ] Error messages don’t expose stack traces or internal details to users
  • [ ] Rate limiting exists on auth endpoints

Most AI-generated codebases fail three or more of these. Not because the developer is careless , because the tool optimizes for different things than this list does.

The Bigger Point

Vibe coding isn’t going away. The speed is too good, the leverage is too real. We’re not going back to writing everything by hand.

But we’re entering a phase where the gap between “the AI wrote it and it works” and “the AI wrote it and it’s safe” is becoming expensive. Users are smarter. Attackers are faster. The standards for what a launched product means are going up.

Build fast. That’s the point. Just put something between your AI tools and your users that thinks adversarially about what they built.

Your users shouldn’t be your QA team.

If you’re building on Cursor, Bolt, v0, Replit, Windsurf, or any AI stack run the free scan at Assayer.dev before April 5. Takes about two minutes to connect your repo.

Drop questions or your own AI code horror stories in the comments , genuinely curious what patterns others are seeing.

https://assayer.dev/blog

Entity vs DTO vs Model — Stop Using These Terms Interchangeably

Do you ever confuse yourself with the terms Entity, Model, and DTO? Sometimes you might hear your lecturer say that you can call an Entity a Model, but is that really true?
They may sound similar, but they are actually quite different. Let’s understand what they really are and what they actually do.

Entity

An Entity represents a database table using annotations like @entity. Think of it as a real table in your database.
Example: Student Table

@Entity
@Table(name = "students")
public class StudentEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String email;
    private String password;
}

An Entity is used by JPA/Hibernate. It is tightly coupled to the database and typically contains all fields, including sensitive ones such as passwords.

DTO (Data Transfer Object)

A DTO is used to transfer data between layers.

It is not directly connected to the database. It is a safer, filtered version of your data that you send to the frontend.

public class StudentDTO {
    private Integer id;
    private String name;
}

Model

This is where many people get confused. In Spring Boot, Model is often used as a general term for business objects. In MVC architecture, a Model is specifically used to pass data to views. Sometimes developers use “Model” in place of “Entity,” so its meaning depends on the context.
In Spring MVC, a Model is simply used to pass data to a view template like Thymeleaf.

@GetMapping("/student")
public String getStudent(Model model) {
    model.addAttribute("name", "Aqueel");
    return "StudentPage";
}

A Simple Analogy — The Toy Shop
Imagine you own a toy shop.
Think of the Entity as your back storage room. It contains everything:

  • All toys
  • Their cost price
  • Supplier details
  • Secret discount rules

That’s your Entity — directly connected to the database.
When customers walk in, you don’t take them to the storage room. You only show them:

  • Toy name
  • Selling price
  • Color

You hide the supplier info and purchase price from them. That filtered view is your DTO — a safe version of the data.
The Model is like the receipt at the counter. When someone buys a toy, it shows:

  • Toy name
  • Quantity
  • Total price

Why Not Just Use Entity Everywhere?

  1. – Security risk — sensitive fields like passwords get exposed.
  2. – Over-fetching — you return more data than the client actually needs.
  3. – Tight coupling — your database structure becomes directly tied to your API.
  4. – Hard to scale — changes to the database will break your API responses.

The Golden Rule:

Use Entity to talk to your database, DTO to talk to the outside world, and Model to talk to your views.

Think of it this way,

Entity = what lives in your database 🗄️
DTO = what you show to the world 🌐
Model = what you pass to your UI 🖥️

Never expose your Entity directly to the frontend. Always filter your data through a DTO. The extra effort keeps your application secure, clean, and scalable.

Found this helpful? ♻️ Repost to help other developers.
Follow for more tips! 🚀

SpringBoot #Java #Backend #WebDevelopment #Programming #DTO #JPA #SoftwareEngineering #Developer

DNS Demystified: Why A, NS, and CNAME Record All Matter

Most developers start their journey with DNS by learning one simple thing:

“Point your domain to an IP using an A record.”

While that works for basic setups, it barely scratches the surface of how DNS actually powers the internet.

If you’ve ever wondered:

Why your domain sometimes doesn’t resolve

Why CDNs don’t give you a fixed IP

Why changing DNS feels confusing

This blog will give you a clear, practical understanding of the three most important DNS records: A, NS, and CNAME.

🧠 The Foundation: What DNS Really Does

DNS (Domain Name System) is essentially the internet’s lookup system.

Instead of remembering IP addresses like 142.250.183.14, we use human-friendly names like google.com.

DNS translates:

Domain → IP Address
But here’s the key insight:

👉 DNS is not just about mapping
👉 It’s about delegation, structure, and flexibility

🔹 1. A Record — The Actual Destination

What it does

An A record (Address record) maps a hostname directly to an IPv4 address.

Example:

myapp.com → 1.2.3.4
Why it matters

This is the final step in DNS resolution:

It tells the browser exactly where your server lives

Without it, users can’t reach your application

Real-world usage

Hosting a website on a VPS

Connecting backend services directly to an IP

🔹 2. NS Record — The Hidden Backbone

What it does

An NS (Name Server) record tells the internet:

“Which DNS server is authoritative for this domain?”

Example:

myapp.com → ns1.provider.com
Why it matters

Before DNS can return an A record, it must first answer:

👉 “Who should I ask for this domain’s records?”

That’s where NS comes in.

🔍 How resolution actually works

When someone visits your site:

Browser asks global DNS:
“Who manages myapp.com?”

NS record responds:
“Ask ns1.provider.com”

That server returns:

A record

CNAME

MX, etc.

Key insight

A record = answer

NS record = where to get the answer

Without NS records:
❌ Your domain won’t resolve
❌ The internet won’t know where your DNS is hosted

🔹 3. CNAME — The Smart Alias System

What it does

A CNAME (Canonical Name) maps one hostname to another hostname.

Example:

www.myapp.com → myapp.com
Why not just use A records?

Let’s say you have:

myapp.com → 1.2.3.4
www.myapp.com → 1.2.3.4
api.myapp.com → 1.2.3.4
Problems:

Repetition

Hard to maintain

Risk of inconsistency

With CNAME:

myapp.com → 1.2.3.4 (A record)
www.myapp.com → myapp.com (CNAME)
api.myapp.com → myapp.com (CNAME)
Benefits:

✅ Single source of truth
✅ Easier updates
✅ Cleaner DNS configuration

🚀 Real-World Scenario: CDNs and Modern Infra

When using providers like Cloudflare:

You often won’t point directly to an IP.

Instead:

www.myapp.com → CNAME → xyz.cloudflare.net
Why?

Because:

IPs are dynamic

Traffic is routed globally

Load balancing happens behind the scenes

👉 This is impossible to manage with just A records

🔥 Putting It All Together

Here’s what a typical setup looks like:

A myapp.com → 1.2.3.4
CNAME www.myapp.com → myapp.com
MX myapp.com → mail.myapp.com
NS myapp.com → ns1.provider.com
🧩 Mental Model (Super Important)

Think of DNS like this:

Domain = your brand name

NS record = which company manages your records

A record = actual server location

CNAME = nickname/alias

⚠️ Common Misconceptions

❌ “A record is enough”

Only for very basic setups. Doesn’t scale.

❌ “NS is optional”

No — it’s foundational. Without it, DNS fails.

❌ “CNAME is just convenience”

It’s essential for:

CDNs

Microservices

Scalable systems

✅ Final Takeaways

A record → tells where your server is

NS record → tells who controls your DNS

CNAME → enables flexibility and scalability

💡 Why This Matters for Engineers

Understanding DNS deeply helps you:

Debug production outages faster

Configure CDNs correctly

Design scalable infrastructure

Avoid common deployment mistakes

DNS isn’t just configuration —
it’s the routing layer of the internet.

Master it once, and you’ll unlock a whole new level of clarity in system design 🚀

DNS #WebDevelopment #SystemDesign #DevOps #BackendEngineering

Full Blog Link: https://quizmaker.co.in/study/chapter/-dns-demystified-why-a-ns-and-cname-record-all-matter

🔥 Top 10 AI Tools Every Developer Should Use in 2026

Artificial Intelligence is changing the way we build software, websites, and apps. As a developer, using the right AI tools can save time, increase productivity, and improve code quality.

Here are the Top 10 AI tools every developer should use in 2026:

1. ChatGPT

Great for code generation, debugging, and learning new concepts.

2. GitHub Copilot

Helps you write code faster with AI suggestions inside your editor.

3. Cursor

An AI-powered code editor for faster development workflows.

4. V0 (by Vercel)

Generate UI components using simple prompts.

5. Replit AI

Build and run projects directly in the browser with AI assistance.

6. Framer AI

Create websites and landing pages using AI.

7. Notion AI

Perfect for documentation, planning, and idea generation.

8. Midjourney / DALL·E

Generate design assets and UI inspiration.

9. Zapier AI

Automate workflows and connect apps easily.

10. Perplexity AI

Great for research and technical answers.

🚀 Final Thoughts

AI is not replacing developers — it’s making them more powerful.
The key is to learn how to use these tools effectively.

At Utilizor, we use AI tools daily to build websites, apps, and scalable digital solutions faster.

💥 I Was Tired of Boring UIs, So I Built a CSS Framework Straight Out of a 1962 Comic Book

Let me ask you something.

When was the last time you visited a website and thought “wow, this UI has a personality”?

Be honest. Because I’ve been building on the web for years, and lately every project I open in Figma — or every starter template I pull from npm — looks identical. A sterile off-white background. Inter font at font-weight: 400. A primary color from a muted blue palette that some product manager once called “trustworthy.” A card with border-radius: 8px and a whisper of a box-shadow.

It’s safe. It’s clean. It’s everywhere.

And I am absolutely exhausted by it.

🎨 The Web Used to Have Soul

There was a time when websites were weird. Aggressively, unapologetically weird. Colors screamed at you. Borders were thick and black like someone drew them with a Sharpie. Things were misaligned in ways that somehow felt intentional and full of energy.

That visual language had a name: it lived in vintage comic books, in Roy Lichtenstein pop art, in the chunky, ink-heavy print design of the 50s, 60s, and yes — even the gloriously chaotic 90s.

So I asked myself: what if I just… brought that back?

Not as a joke. Not as a gimmick. But as a real, usable CSS design system that any developer could drop into a project and instantly make something that felt alive.

That’s how KABOOM! UI was born. 💥

🚀 What is KABOOM! UI?

KABOOM! UI is a 100% pure CSS, zero-dependency design system built to replicate the aesthetic of vintage comic books, Pop Art, and 90s nostalgia.

There is no JavaScript. No build step. No npm install. You download one .css file, drop a <link> tag in your <head>, and you’re immediately writing UI that looks like it was printed on a comic book press.

Heavy ink borders. Misaligned CMYK print shadows. Halftone dot backgrounds. Hand-drawn wobbly aesthetics. It’s all in there, and it weighs almost nothing.

🎨 See the live demo → kaboom-ui-kit.pages.dev

⭐ GitHub Repo → github.com/Gorupa/kaboom-ui-kit

Go check out the demo. I’ll wait. I genuinely think it’ll make you smile.

🛠️ The CSS Tricks That Make It Work

Okay, here’s the part I’m most excited to share — because building this thing taught me that you can achieve a lot of personality with very basic CSS primitives. Let me walk you through two of my favorite techniques.

1. 🔵 Halftone Dot Backgrounds with radial-gradient

If you’ve ever looked closely at a printed comic book, you’ll notice the backgrounds and shading aren’t solid colors — they’re made up of tiny dots. This is called the Ben-Day dot process, and it’s pure visual gold.

Here’s the thing: you don’t need SVGs, images, or a canvas element to fake it. You can do it entirely with CSS using radial-gradient inside background-image:

.k-halftone {
  background-color: #fff;
  background-image: radial-gradient(
    circle,
    #000 1.5px,
    transparent 1.5px
  );
  background-size: 10px 10px;
}

What’s happening here? The radial-gradient draws a tiny filled circle (1.5px radius, solid black) at the center of each tile, and then fades to transparent for the rest of the space. The background-size controls how far apart the dots are. Make the tile smaller and the dots get denser. Make the dot radius larger and you get that bold, chunky comic book look.

Tweak the dot color, tile size, and radius, and you can create everything from a subtle texture to a screaming pop-art background. No images, no JavaScript, no dependencies — just math and gradients. 🎨

2. 🟡 Misaligned Print Shadows with box-shadow

This is my personal favourite trick in the whole framework. If you look at cheaply printed comic books from the 60s, you’ll notice that the color plates didn’t always line up perfectly with the black ink outlines. This created a gorgeous off-register shadow effect — a halo of cyan or magenta peeking out from behind the black border.

You can fake this almost perfectly with a solid, no-blur box-shadow:

.k-card {
  border: 3px solid #000;
  box-shadow: 5px 5px 0px #000;
}

The key here is blur-radius: 0. A standard drop shadow uses blur to create a soft, realistic fade. But set it to zero and the shadow becomes a solid, hard-edged duplicate of your element — offset a few pixels down and to the right. It looks exactly like a second printing plate that didn’t quite line up.

Want to push it even further into “cheap print” territory? Stack multiple shadows and use CMYK-inspired colors:

.k-card--pop {
  border: 3px solid #000;
  box-shadow:
    4px 4px 0px #00bcd4,   /* cyan plate — slightly offset */
    7px 7px 0px #000;       /* black plate — the outermost shadow */
}

Now your element looks like it was legitimately pulled from a Roy Lichtenstein print. For zero lines of JavaScript. 💥

✍️ How Easy Is It to Actually Use?

This was a core design goal: getting from zero to comic-book in under 60 seconds. Here’s all you need:

Step 1 — Link the stylesheet

<link rel="stylesheet" href="comic-ui.css" />

Step 2 — Use the classes

<!-- A comic-book style card -->
<div class="k-card">
  <div class="k-card__badge">HOT TAKE #7</div>
  <h2 class="k-card__title">Flat Design Is Overrated</h2>
  <p class="k-card__body">
    I said what I said. Give me ink borders or give me death.
  </p>
  <button class="k-btn">Fight Me →</button>
</div>

<!-- A halftone hero section -->
<section class="k-halftone">
  <h1 class="k-splash-title">POW!</h1>
  <p>No npm install required. Incredible.</p>
  <button class="k-btn k-btn--danger">KABOOM!</button>
</section>

That’s it. No configuration. No theme tokens to set up. No tailwind.config.js. Just classes that do exactly what they sound like.

🚀 Go Build Something Weird

Here’s the deal.

The Free “Sidekick” Tier is fully open-sourced on GitHub right now. It includes the core components — buttons, cards, badges, speech bubbles, halftone backgrounds, and the full ink border and offset shadow system. Everything you need to build something that turns heads.

👉 Grab it on GitHub → github.com/Gorupa/kaboom-ui-kit

If you want to go deeper, there’s also a Premium “Superhero” Tier available on the demo site. It unlocks 50+ components, ready-to-use full HTML page templates (landing pages, pricing pages, portfolios), and the Figma source files — for the designers in the room who want to prototype with the same energy.

👉 Check out the full demo → kaboom-ui-kit.pages.dev

💬 Now I Want to Hear From You

I built KABOOM! UI because I genuinely believe the web gets more interesting when we stop optimizing purely for “professional” and start leaving a little room for fun.

A few things I’d love your feedback on:

  • What components should I build next? Speech bubbles? Comic-strip grid layouts? A villain-red alert banner? Drop your ideas in the comments.
  • Does the aesthetic resonate? Are there other nostalgic design eras you’d love to see explored in CSS?
  • Any CSS tricks you’d add? I’m always looking to learn — if you’ve got a cool technique that fits the vibe, share it below.

And if the halftone dots and ink borders put a smile on your face — even a small one — it would genuinely mean the world if you dropped a ⭐ on the GitHub repo. It helps other developers find it, and it lets me know this weird little project is worth continuing.

Now go make something that doesn’t look like every other website on the internet. 🥊

Happy to answer any questions about the CSS techniques used or the design decisions behind the framework. Ask me anything in the comments!

“Self-Hosting Stripe Anomaly Detection: Building a Multi-Tenant BillingWatch with FastAPI”

Why Self-Host Billing Monitoring?

SaaS billing monitoring tools exist. They’re fine. They also cost money, send your billing data to someone else’s servers, and lock you into their alerting UX. If you’re running multiple Stripe accounts — or just care about controlling sensitive financial telemetry — self-hosting is worth the hour of setup.

BillingWatch is what I built to replace a paid tool: a FastAPI + SQLite stack that ingests Stripe webhooks, runs 7 real-time anomaly detectors, and alerts you when something looks wrong. Multi-tenant by default.

What BillingWatch Detects

Seven detectors ship out of the box:

  1. Charge Failure Spike — unusual jump in failed charges vs. baseline
  2. Duplicate Charge — same customer, same amount, short time window
  3. Fraud Spike — surge in charge.dispute.created events
  4. Revenue Drop — successful charge volume drops below rolling average
  5. Silent Lapse — no webhook activity for N hours (dead webhook endpoint)
  6. Webhook Lag — event timestamp vs. received-at gap exceeds threshold
  7. Invoice Mismatch — invoice amount doesn’t match subscription plan amount

Each detector is independently configurable per tenant — you can set different thresholds for your production account vs. a staging account.

Architecture

BillingWatch/
├── app/
│   ├── main.py           # FastAPI app, webhook endpoint
│   ├── detectors/        # One file per anomaly type
│   │   ├── charge_failure.py
│   │   ├── duplicate_charge.py
│   │   └── ...
│   ├── models.py         # SQLite schema (SQLAlchemy)
│   └── alerts.py         # Alert routing (email, Slack, webhook)
├── docker-compose.yml
└── .env.example

FastAPI handles the ingestion. SQLite stores everything locally — no external DB required. Docker Compose wires it up.

Webhook Signature Verification

This is non-negotiable with Stripe. Every webhook must be verified before processing:

import stripe
from fastapi import FastAPI, Request, HTTPException, Header
from typing import Optional

app = FastAPI()

@app.post("/webhooks/{tenant_id}")
async def receive_webhook(
    tenant_id: str,
    request: Request,
    stripe_signature: Optional[str] = Header(None)
):
    payload = await request.body()

    # Get this tenant's webhook secret
    webhook_secret = get_tenant_webhook_secret(tenant_id)

    try:
        event = stripe.Webhook.construct_event(
            payload=payload,
            sig_header=stripe_signature,
            secret=webhook_secret
        )
    except stripe.error.SignatureVerificationError:
        raise HTTPException(status_code=400, detail="Invalid signature")
    except ValueError:
        raise HTTPException(status_code=400, detail="Invalid payload")

    # Store and process
    store_event(tenant_id, event)
    run_detectors(tenant_id, event)

    return {"status": "ok"}

Each tenant gets their own webhook endpoint URL (/webhooks/<tenant_id>) and their own Stripe webhook secret. This is how multi-tenancy works — complete isolation at the HTTP layer.

Multi-Tenant Setup

Tenants are just rows in a config table:

from sqlalchemy import Column, String, JSON
from .database import Base

class Tenant(Base):
    __tablename__ = "tenants"

    id = Column(String, primary_key=True)  # e.g. "prod", "staging", "client-acme"
    name = Column(String)
    stripe_webhook_secret = Column(String)
    thresholds = Column(JSON)  # Per-tenant detector config

# Default thresholds
DEFAULT_THRESHOLDS = {
    "charge_failure_spike": {"multiplier": 3.0, "window_hours": 1},
    "duplicate_charge": {"window_seconds": 300},
    "fraud_spike": {"multiplier": 5.0, "window_hours": 24},
    "revenue_drop": {"drop_pct": 50, "window_hours": 24},
    "silent_lapse": {"max_silence_hours": 4},
    "webhook_lag": {"max_lag_seconds": 30},
    "invoice_mismatch": {"tolerance_cents": 0}
}

To add a new tenant:

curl -X POST http://localhost:8000/tenants 
  -H "Content-Type: application/json" 
  -d '{"id": "prod", "name": "Production", "stripe_webhook_secret": "whsec_..."}'

Then register the BillingWatch webhook URL in your Stripe dashboard under https://yourdomain.com/webhooks/prod.

A Detector Example: Charge Failure Spike

from datetime import datetime, timedelta
from ..models import StripeEvent, session

class ChargeFailureDetector:
    def run(self, tenant_id: str, event: dict, thresholds: dict):
        if event['type'] != 'charge.failed':
            return None

        config = thresholds.get('charge_failure_spike', {})
        window = timedelta(hours=config.get('window_hours', 1))
        multiplier = config.get('multiplier', 3.0)

        # Count recent failures
        cutoff = datetime.utcnow() - window
        recent_failures = session.query(StripeEvent).filter(
            StripeEvent.tenant_id == tenant_id,
            StripeEvent.event_type == 'charge.failed',
            StripeEvent.received_at >= cutoff
        ).count()

        # Compare to baseline (same window, 7 days ago)
        week_ago_cutoff = cutoff - timedelta(days=7)
        baseline_failures = session.query(StripeEvent).filter(
            StripeEvent.tenant_id == tenant_id,
            StripeEvent.event_type == 'charge.failed',
            StripeEvent.received_at.between(week_ago_cutoff, week_ago_cutoff + window)
        ).count()

        if baseline_failures > 0 and recent_failures >= (baseline_failures * multiplier):
            return {
                "type": "charge_failure_spike",
                "severity": "high",
                "detail": f"{recent_failures} failures vs {baseline_failures} baseline"
            }

        return None

Each detector returns None (no anomaly) or a dict (fire an alert). Clean, testable, easy to add new ones.

Docker Compose

version: '3.8'
services:
  billingwatch:
    build: .
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=sqlite:///./billingwatch.db
    volumes:
      - ./data:/app/data
    restart: unless-stopped
git clone https://github.com/rmbell09-lang/BillingWatch
cp .env.example .env
docker-compose up -d

Local Testing With Stripe CLI

# Install Stripe CLI
brew install stripe/stripe-cli

# Forward real Stripe events to local BillingWatch
stripe listen --forward-to localhost:8000/webhooks/dev

# Trigger a test event
stripe trigger charge.failed

The Stripe CLI outputs the webhook signing secret you’ll need for local testing — drop it in your .env as DEV_WEBHOOK_SECRET.

What It Looks Like in Practice

After running for a few weeks on a production Stripe account:

  • Caught 2 duplicate charge attempts (card testing, both auto-blocked by Stripe but good to know)
  • Silent lapse alert fired once when Stripe had a webhook delivery outage — useful for knowing “is this us or them?”
  • Revenue drop detector needed threshold tuning — first week had false positives on low-traffic days

The tuning overhead is real but worth it. After calibrating thresholds to your actual traffic patterns, it’s quiet — and when it fires, it means something.

GitHub

Full source at github.com/rmbell09-lang/BillingWatch. Issues and contributions welcome, especially new detector implementations.

Automated Internal Linking with Tag-Based Content Graphs

Why Internal Links Matter

Internal links distribute page authority, help Google discover content, and keep users engaged. For 80+ articles, manual linking is unsustainable.

Tag-Based Link Graph

Every article has tags in frontmatter. The algorithm finds related articles by tag overlap:

function buildLinkMap(articles) {
  const linkMap = {};
  for (const article of articles) {
    const related = articles
      .filter(a => a.slug !== article.slug)
      .map(a => ({
        slug: a.slug,
        overlap: a.tags.filter(t => article.tags.includes(t)).length
      }))
      .filter(a => a.overlap > 0)
      .sort((a, b) => b.overlap - a.overlap)
      .slice(0, 6);
    linkMap[article.slug] = related;
  }
  return linkMap;
}

Output Format

The script generates a TypeScript file with related articles that components import for rendering.

Dry Run Mode

Run with –dry-run to preview changes before committing.

Impact

After implementing automated internal linking:

  • Average internal links per article: 1.2 to 5.4
  • Pages with zero internal links: 23 to 0
  • Google discovered 17 more pages within two weeks

“How I Built an Overnight Strategy Tournament System for Algorithmic Paper Trading”

The Problem With Testing Trading Strategies

As a trader, testing multiple strategies is brutal. Manual backtesting is slow, inconsistent, and you can’t run 10 strategies simultaneously while you sleep. Most people end up with gut-feel decisions dressed up as analysis.

I built TradeSight to fix this: an overnight strategy tournament that runs your strategies in parallel, ranks them by actual performance metrics, and hands you a leaderboard in the morning.

The Tournament Concept

The core idea is simple — pit your strategies against each other on real market data (paper trading via Alpaca), rank them by Sharpe ratio + win rate, and let the best ones survive. Think evolutionary pressure, but for trading algos.

Each night:

  1. All registered strategies fetch OHLCV data for a watchlist of tickers
  2. Each strategy computes buy/sell signals using its own indicator logic
  3. Paper orders are submitted to Alpaca
  4. At morning close, results are scored and ranked on the dashboard

Architecture

Four components keep this simple:

  • Flask Dashboard — configure tournaments, view live positions, leaderboard
  • Strategy Runner — executes strategies in parallel (threading, not async — simpler for this use case)
  • Alpaca Integration — paper trading API for order submission and position tracking
  • Cron Automation — launches the tournament at market close, scores at open
TradeSight/
├── app/
│   ├── dashboard.py      # Flask routes
│   ├── runner.py         # Strategy execution engine
│   └── alpaca_client.py  # Alpaca API wrapper
├── strategies/
│   ├── base.py           # Strategy base class
│   ├── macd_strategy.py
│   └── rsi_strategy.py
└── cron/
    └── overnight_run.sh

Defining a Strategy

Every strategy inherits from Strategy and implements two methods:

from strategies.base import Strategy
import numpy as np

class MACDStrategy(Strategy):
    name = "MACD Crossover"

    def compute_signals(self, ohlcv_data):
        """Return array of 1 (buy), -1 (sell), 0 (hold)."""
        close = ohlcv_data['close'].values

        # EMA calculations
        ema12 = self._ema(close, 12)
        ema26 = self._ema(close, 26)
        macd_line = ema12 - ema26
        signal_line = self._ema(macd_line, 9)

        # Crossover detection
        signals = np.zeros(len(close))
        for i in range(1, len(macd_line)):
            if macd_line[i] > signal_line[i] and macd_line[i-1] <= signal_line[i-1]:
                signals[i] = 1   # bullish crossover
            elif macd_line[i] < signal_line[i] and macd_line[i-1] >= signal_line[i-1]:
                signals[i] = -1  # bearish crossover

        return signals

    def submit_orders(self, signals, symbol, qty=10):
        """Submit paper orders based on signals."""
        latest = signals[-1]
        if latest == 1:
            self.alpaca.submit_order(symbol=symbol, qty=qty, side='buy', type='market')
        elif latest == -1:
            self.alpaca.submit_order(symbol=symbol, qty=qty, side='sell', type='market')

TradeSight currently ships with 6 strategies out of the box:

  • MACD Crossover
  • RSI Overbought/Oversold
  • Bollinger Band Squeeze
  • EMA Ribbon
  • Volume Spike + Price Confirmation
  • Mean Reversion (Z-score based)

The Paper Trading Loop

def run_tournament(symbols, strategies):
    results = []

    for strategy in strategies:
        strategy_results = []
        for symbol in symbols:
            # Fetch OHLCV from Alpaca
            bars = alpaca.get_bars(symbol, TimeFrame.Minute, limit=200)
            ohlcv = pd.DataFrame([b._raw for b in bars])

            # Get signals
            signals = strategy.compute_signals(ohlcv)

            # Submit paper orders
            strategy.submit_orders(signals, symbol)

            # Track for scoring
            strategy_results.append({
                'symbol': symbol,
                'signal': signals[-1],
                'entry_price': ohlcv['close'].iloc[-1]
            })

        results.append({'strategy': strategy.name, 'trades': strategy_results})

    return results

Scoring and the Leaderboard

After positions close, each strategy is scored:

def score_strategy(trades):
    winning_trades = [t for t in trades if t['pnl'] > 0]
    win_rate = len(winning_trades) / len(trades) if trades else 0

    returns = [t['pnl_pct'] for t in trades]
    sharpe = (np.mean(returns) / np.std(returns)) * np.sqrt(252) if returns else 0

    # Combined score: weights Sharpe more than win rate
    score = (sharpe * 0.6) + (win_rate * 0.4)
    return {'sharpe': sharpe, 'win_rate': win_rate, 'score': score}

The Flask dashboard shows a live leaderboard, current open positions, and historical tournament results so you can watch strategies improve (or die) over time.

Adding Your Own Strategy

Drop a file in strategies/, inherit from Strategy, implement compute_signals and submit_orders. That’s it. The tournament runner auto-discovers all strategy classes on startup.

class MyCustomStrategy(Strategy):
    name = "My Custom Strategy"

    def compute_signals(self, ohlcv_data):
        # Your logic here
        pass

    def submit_orders(self, signals, symbol, qty=10):
        # Your execution logic here
        pass

What I Learned

Running overnight tournaments for a few weeks taught me more about strategy behavior than months of manual backtesting. The biggest insight: strategies that look good on paper often collapse on live data because of execution timing. Paper trading isn’t perfect, but it’s a lot closer to reality than backtesting on historical data with perfect hindsight.

The tournament format also surfaces something important: consistency beats peak performance. A strategy that wins 60% of tournaments is more valuable than one that wins one tournament spectacularly and fails the rest.

GitHub

The full source is at github.com/rmbell09-lang/tradesight. Stars and PRs welcome — especially new strategy implementations.

Getting Started with Seal Report: Applying Dependant Filters

vladg_dev profile

This is the fourth report in the series.

So far, we’ve used different types of restrictions, but those were independent of each other. However, real-world filtering scenarios often require dependent restrictions, where one selection affects the available values of another.

Within the AdventureWorks2025 open-source database, there is a classic example of such dependency: CountryRegion → StateProvince.

Applying this type of filter allows us to narrow down employees based on the province associated with one of their recorded addresses, while ensuring that only provinces belonging to the selected country are available for selection.

In the previous post, we examined static and dynamic select lists as data table restrictions.

In this article, we will configure dependent select lists so that the available StateProvince values automatically adjust based on the selected CountryRegion.

Adding Necessary Data Tables from the Catalog

To accomplish the desired task, several data tables must first be imported from the Catalog. The process was described in the first article of this series:

For the current scenario, the following data tables need to be imported from the AdventureWorks2025 database:

  • Person.CountryRegion,
  • Person.StateProvince,
  • Person.Address,
  • Person.BusinessEntityAddress,
  • Person.BusinessEntity.

These tables allow us to build the relationship chain required to filter employees by the province associated with their addresses, while ensuring that the selected province belongs to the chosen country.

If the Auto create joins option was enabled during import, the records in the Joins section should look similar to the following:

Applied Auto Joins on Imported Data Tables from the Catalog

Adding the Main Enumeration

The process is straightforward. Add a dynamic enumeration using the following SQL Select Statement:

SELECT
    CountryRegionCode AS [KEY],
    Name AS [VALUE]
FROM Person.CountryRegion
ORDER BY Name

Because the values are retrieved from the query, the enumeration must be configured to load its content dynamically from the SQL Select Statement and refreshed before report execution.

I’ve named this enumeration as CountryRegion. This enumeration name will be referenced later when configuring the dependent select list.

Configuring the Dynamic Main Enumeration

Next, open Models → Model and drag the CountryRegionCode property from the Person.CountryRegion data table into the Restrictions section.

The newly created restriction can now be linked to the CountryRegion enumeration by selecting it as the custom enumerated list from the Advanced configuration panel.

Finally, enable Prompt at execution so the restriction becomes available when running the report.

Adding the Dependant Enumeration

Things become slightly more interesting when configuring the dependent enumeration.

At first glance, its main configuration looks similar to the previous example. The base SQL Select Statement still loads all available values and does not reference any restrictions:

SELECT
    StateProvinceID as [KEY],
    Name as [Value]
FROM Person.StateProvince
ORDER BY Name

The dependency itself is defined separately, inside Dynamic display → SQL Select Statement for prompted restriction:

SELECT
    StateProvinceID as [Key],
    Name as [Value]
FROM Person.StateProvince
WHERE CountryRegionCode IN ({EnumValues_CountryRegion})
ORDER BY Name

The dependency is implemented inside the SQL Select Statement for prompted restriction, where the selected values of the CountryRegion restriction are referenced using the {EnumValues_CountryRegion} pattern. This ensures that only the provinces belonging to the selected country remain available in the StateProvince enumeration.

Configuration of Dependant Enumeration

The restriction itself is then added to the report in the same way as in the previous examples, so no additional configuration is required at the model level.

Skill Check

If everything has been configured as described in this post:

  • initially, the StateProvince filter remains empty because no country or region has yet been selected in the CountryRegion filter:

By Default, the Dependant Select List is Empty

  • after selecting a region such as United Kingdom, the dependent filter is refreshed automatically and displays only the corresponding provinces:

Dependant Select List is Refreshed Automatically

As an additional exercise, try making the dependent filter required and configure a display message indicating that a country or region must be selected first.

So far, all examples in this series have been based on data tables as the underlying data source. However, real-life reporting scenarios often rely on stored procedures and user-defined functions. In the next post, we’ll explore how the same filtering techniques can be applied when working with those types of sources.