I Built a Free Supplement Stack Checker That Detects 102 Interactions

If you take more than one supplement, you might be unknowingly creating problems. Iron blocks zinc absorption. Calcium competes with magnesium. Vitamin E at high doses can thin blood dangerously when combined with fish oil.

I built a free tool to catch these issues before they happen: a Supplement Stack Checker that analyzes combinations of supplements and flags interactions, absorption conflicts, and timing issues.

Why I Built This

I work on Botánica Andina, an evidence-based resource about medicinal plants and supplements for Latin American audiences. While building our herb-drug interaction checker, I realized there was a gap: people taking multiple supplements had no easy way to check if their “stack” was safe.

The problem is real. A 2023 survey by the Council for Responsible Nutrition found that 75% of U.S. adults take dietary supplements, and the average user takes 3-4 different products. But most people never check for interactions between them.

Existing tools either focus on drug-drug interactions (ignoring supplements entirely) or are locked behind paywalls. I wanted something free, fast, and focused specifically on supplement-supplement interactions.

How It Works

The architecture is deliberately simple — everything runs client-side in vanilla JavaScript. No frameworks, no build step, no backend API calls. Here’s why:

The Data Layer

The core is a JSON database of 78 common supplements with 102 documented interactions. Each interaction has:

{
  "supplement_a": "iron",
  "supplement_b": "zinc",
  "type": "absorption_competition",
  "severity": "moderate",
  "mechanism": "Both compete for DMT1 transporter in the intestinal lumen",
  "recommendation": "Take at least 2 hours apart",
  "sources": ["J Nutr 2003;133:1S", "Am J Clin Nutr 2005;81:S"]
}

Every interaction is sourced from peer-reviewed literature — primarily from PubMed, the Natural Medicines database, and systematic reviews. No blog posts, no anecdotes.

The Matching Algorithm

When a user selects their supplements, the checker runs pairwise comparisons across the selected set. For a stack of N supplements, that’s N×(N-1)/2 pairs to check.

function analyzeStack(selectedSupplements) {
  const warnings = [];
  for (let i = 0; i < selectedSupplements.length; i++) {
    for (let j = i + 1; j < selectedSupplements.length; j++) {
      const interaction = findInteraction(
        selectedSupplements[i],
        selectedSupplements[j]
      );
      if (interaction) {
        warnings.push({
          pair: [selectedSupplements[i], selectedSupplements[j]],
          ...interaction
        });
      }
    }
  }
  return warnings;
}

Timing Optimization

Beyond just flagging conflicts, the tool suggests optimal timing. Some supplements are better absorbed on an empty stomach (iron, zinc), while others need fat for absorption (vitamins A, D, E, K). The timing engine groups supplements into morning (with food), morning (empty stomach), evening, and bedtime slots.

Zero Dependencies, Single File

The entire tool — HTML, CSS, JavaScript, and data — ships as a single HTML file under 60KB. No external requests, no tracking scripts, no cookies. It loads in under 1 second on a 3G connection.

This wasn’t just a design preference. For our Latin American audience, many users are on mobile with limited data plans. Every kilobyte matters.

Interesting Findings from the Data

Building the interaction database surfaced some surprising patterns:

1. The “healthy” stack can backfire. A common wellness stack — Vitamin C + Iron + Calcium + Zinc — has three conflicts: Calcium blocks iron absorption by 50-60%. Zinc and iron compete for the same transporter. Only Vitamin C + Iron is genuinely synergistic (C enhances iron absorption by 2-3x).

2. Fat-soluble vitamins compete. High-dose Vitamin A reduces Vitamin D absorption, and vice versa. Taking a multivitamin with separate vitamin D drops can reduce effectiveness of both.

3. Timing solves most problems. 70% of the interactions in our database can be mitigated by simply separating doses by 2+ hours. The issue isn’t that you can’t take both — it’s that you shouldn’t take them together.

Technical Decisions

Why not a database/API? For a tool this size, a backend adds latency, hosting costs, and a failure point. The entire interaction database fits in ~15KB of JSON. Client-side matching runs in under 5ms even on old phones.

Why vanilla JS? The tool does one thing. React/Vue/Svelte would add 30-100KB of framework code for zero benefit. The DOM manipulation is straightforward — render a list of supplements, handle clicks, display results.

Why not use an LLM? Accuracy. For health-related tools, deterministic lookups against curated data are safer than probabilistic language model outputs. Every interaction in our database has citations. An LLM might hallucinate an interaction that doesn’t exist, or worse, miss one that does.

What I Learned

  1. Single-file tools are underrated. No build step, no deployment pipeline, no infrastructure costs. scp index.html server: and you’re live.

  2. Health data curation is the hard part. Writing the code took 2 days. Curating and verifying 102 interactions against primary literature took 2 weeks.

  3. Simple tools get shared. Our interaction checker gets 10x more organic traffic than any blog post. People link to tools. They bookmark tools. They don’t bookmark articles.

Try It

The tool is free and always will be. If you find an interaction we’re missing, or spot an error, I’d love to hear about it.

This is part of Botánica Andina, an evidence-based resource about Andean medicinal plants and natural health for Latin American audiences.

I Built an Andean Plant Quiz That Recommends Medicinal Herbs Based on Your Health Profile

Have you ever wondered which Andean medicinal plant might help with your specific health concern? I built a free interactive quiz that recommends plants like maca, uña de gato, or muña based on your lifestyle and needs.

Why I Built This

I work on Botánica Andina, an evidence-based encyclopedia of Andean medicinal herbs. We already had a herb-drug interaction checker that cross-references 503+ documented interactions — but users kept asking: “Which plant should I actually try?”

The problem is that most “which supplement should I take” quizzes are thinly disguised product pages. I wanted to build something that:

  1. Recommends based on published research, not marketing
  2. Links to actual PubMed studies for every recommendation
  3. Warns about contraindications (not just benefits)
  4. Works entirely client-side — no data collection, no tracking

How It Works

The quiz asks 7 questions about:

  • Your primary health goal (energy, immunity, digestion, stress, etc.)
  • Current medications (to flag interactions)
  • Dietary preferences (vegan-compatible options)
  • Age range and activity level

The Scoring Algorithm

Each Andean plant has a profile vector across health dimensions:

const plants = {
  maca: {
    energy: 0.9, immunity: 0.3, digestion: 0.2,
    stress: 0.6, cognition: 0.5, hormonal: 0.8,
    contraindications: ["hormone-sensitive conditions", "thyroid disorders"]
  },
  cat_claw: {
    energy: 0.2, immunity: 0.9, digestion: 0.4,
    stress: 0.1, cognition: 0.2, hormonal: 0.1,
    contraindications: ["immunosuppressants", "anticoagulants"]
  },
  muna: {
    energy: 0.3, immunity: 0.4, digestion: 0.9,
    stress: 0.3, cognition: 0.2, hormonal: 0.1,
    contraindications: []
  }
  // ... 12 plants total
};

User answers create a “need vector” that gets dot-producted against each plant profile. The top 3 matches get shown with:

  • A confidence score (how well the plant matches your profile)
  • Key benefits backed by specific studies
  • Warnings if any of your medications interact

The Interaction Safety Check

This is the part I’m most proud of. Before showing results, the quiz cross-references your medication list against our interaction database:

function checkSafety(plant, medications) {
  const interactions = interactionDB[plant.id] || [];
  const warnings = [];

  for (const med of medications) {
    const match = interactions.find(i =>
      i.drug_class.some(dc => med.toLowerCase().includes(dc))
    );
    if (match) {
      warnings.push({
        severity: match.severity,
        mechanism: match.mechanism,
        source: match.pubmed_id
      });
    }
  }
  return warnings;
}

If a severe interaction is detected, the plant gets removed from recommendations entirely — not just flagged. I’ve seen too many supplement sites that bury warnings in footnotes.

Technical Stack

The entire quiz runs as a single HTML file — zero dependencies, no build step:

  • Vanilla JavaScript (~400 lines)
  • CSS animations for smooth transitions between questions
  • Schema.org FAQPage markup for SEO
  • Responsive design (works on mobile)

Total page weight: 18KB (including all plant data). Lighthouse score: 100/100 across all categories.

Why No Framework?

I considered React, but for a 7-question quiz with static data, a framework adds complexity without benefit. The entire state is just:

let currentQuestion = 0;
let answers = {};

No need for Redux, Context, or even useState. Sometimes the best framework is no framework.

What I Learned

1. Ethnobotany data is messy

Plant names vary wildly across regions. “Uña de gato” in Peru is Uncaria tomentosa, but in Mexico it refers to a completely different plant (Mimosa pudica). I had to normalize everything to binomial nomenclature to avoid confusion.

2. Confidence scoring needs calibration

My first version used simple keyword matching, which led to maca being recommended for literally everything (it scores moderately on every axis). I added normalization so plants only rank high when they have a strong signal, not just an average-across-the-board signal.

3. Medical disclaimers are non-negotiable

Even with “this is not medical advice” banners, I added specific contraindication checks that actively prevent dangerous recommendations. The quiz refuses to recommend uña de gato if you’re on immunosuppressants, period. No checkbox can override this.

Results So Far

The quiz has been live for 3 weeks:

  • Average completion rate: 89% (most people finish all 7 questions)
  • Most common health goal: “Energy and fatigue” (34%)
  • Most recommended plant: Maca (28% of results)
  • Interaction warnings triggered: 12% of sessions

The high completion rate surprised me — I expected drop-off after question 3-4. I think the progress bar and the “personalized” feel keeps people engaged.

Try It

Take the Andean Plant Quiz

It takes about 90 seconds. No signup, no email collection, no tracking.

If you’re interested in the herb-drug interaction data behind it, check out the Interaction Checker — it covers 503+ documented interactions between medicinal herbs and pharmaceutical drugs, all sourced from PubMed, EMA, and WHO databases.

The full plant database (12 Andean species with evidence profiles) is available on Botánica Andina.

What health tools have you built? I’d love to hear about projects at the intersection of tech and wellness.

Connecting PowerBI to a PostgreSQL database

PowerBI desktop

PowerBI is a business intelligence (BI) tool developed by Microsoft, designed to help users visualize data, create interactive dashboards and make informed decisions.
It is widely used across industries because it simplifies complex data and presents it in a visually compelling way.
PowerBI enables data analysts to :

  • Connect to multiple data sources
  • Transform and clean data
  • Do Data modelling
  • Build interactive dashboards and reports
  • Share insights across teams and organizations

One of the strengths of PowerBI is its ability to connect to multiple data sources, particularly SQL databases.

SQL (Structured Query Language) databases are the backbone of modern data storage systems.
SQL databases are very essential because they:

  • Store large volumes of structured data
  • Ensure data integrity and consistency
  • Allow efficient querying and data retrieval
  • Support complex analytical operations

By connecting PowerBI to SQL databases, companies can unlock real-time insights, improve data accuracy and streamline their analytics workflow.

In this article, we will walk through how to connect Power BI to both a local PostgreSQL database and a cloud-hosted PostgreSQL database (Aiven), and how to model your data effectively inside Power BI.

Connecting PowerBI to a local PostgreSQL database
Let us first see how to connect PowerBI to a local PostgreSQL database.

Step 1: Open Power BI Desktop Application
Launch Power BI Desktop on your computer if you have it installed. If not you can download it from the Microsoft store here PowerBI Desktop download link

PowerBI desktop

Step 2: Click “Get Data”
On the Home ribbon, click Get Data to view all available data sources.

Home ribbon, Get Data

Step 3: Select PostgreSQL Database
Choose the database category, then select PostgreSQL Database

select PostgreSQL Database
After selecting the PostgreSQL, press Connect.

Step 4: Enter Connection Details
After pressing Connect, you will be prompted to enter your postgreSQL connection details.

Connection details required

You will have to check your local postgreSQL connection to check on the connection details i.e; right click on your database then select Edit connection, it will bring you to this page;

Local postgreSQL connection
From this page you are able to get the server name(host), database, port and other information.
Server: localhost:5432 (you put your server name:then port)
Database: your database name.

Enter server name and database name

After putting your server name and database name, click OK.

Step 5: Provide Credentials
Then Enter your Username and password from your local postgreSQL connection.
It should look like this;

Entering username and password
Then Click Connect.

Step 6: Load Data
After Clicking Connect, Power BI will display a Navigator window showing available tables on the left.

PowerBI Navigation window
Select the tables you need by ticking their respective checkboxes.
For me I’m choosing the tables;

  • customers
  • products
  • sales
  • inventory

My selected tables have “assignment.” before their names because they are in a schema called assignment.

After selecting your tables, you can choose to load
or Transform Data or Cancel depending on what you want to with the data first.
For my case I’m selecting Load.
After Selecting Load, the tables then get loaded to your PowerBI and you can be able to perform data cleaning, data validation, data modelling, data analysis and visualization.

Tables in PowerBI
You can navigate from the right side to select your tables and work on them.

Connecting PowerBI to a cloud database-Aiven PostgreSQL database
Cloud databases are increasingly popular today because they allow us to have remote access to our databases, ensure scalability and ensure high availability of our databases.
Aiven provides managed PostgreSQL services in the cloud.

Step 1: Go to Aiven and check for your PostgreSQL database connection if you have one. If you don’t have one then create a new database connection.

aiven PostgreSQL connection
If you have a connection, open it, if you don’t have one then Create service from the top right corner.
Open your SQL connection from Aiven by double clicking it. Then go to Connection information.

Aiven PostgreSQL connection information

From the Connection Information, check for:

  • Host (e.g., your-db.aivencloud.com)
  • Port
  • Database name
  • Username
  • Password

Step 2: Download SSL Certificate
Aiven enforces secure connections using SSL (Secure Sockets layer).
Download the CA Certificate (usually a .pem file) from your Aiven service dashboard.

Downloading CA certificate

After clicking the CA certificate download, it will be downloaded and you have to save it in a secure and accessible location on your computer.
SSL is Important because it ensures:

  • Data encryption during transmission
  • Protection against unauthorized access
  • Secure communication between Power BI and the cloud database.
    Without SSL, your data could be exposed to security risks.

Step 3: Install PostgreSQL ODBC Driver
To be able to connect PowerBI to an aiven PostgreSQL database, you need a PostgreSQL ODBC Driver.
You can download it using the link Download PostgreSQL ODBC Driver here
After installing in, open the ODBC Data Sources from your computer.

Step 4: Create an ODBC connection
After opening ODBC Data Sources
Go to System DSN then click Add
Select PostgreSQL Unicode(x64)

Selecting PostgreSQL Unicode(x64)

Click Finish then it will open up the window below.
Input your connection details from your Aiven PostgreSQL database as shown below;

Step 5: Configure SSL
Enable SSL mode by Setting SSL Mode to require
Provide the certificate file path if required

Click Test to see if your connection is Okay. If Connection is okay, proceed and save. If not recheck your connection details

Testing connection

Step 6: Launch PowerBI and Get data from your ODBC
After saving, close it the go and open your PowerBI desktop.
Go to Get Data and search for ODBC

Select ODBC
Select ODBC and click Connect
Select your Data source Name and click okay

selecting Data source Name

Then input your username and password from your Aiven connection then press Connect.
Step 6: Select your tables from the Navigator
A navigator window will open up. Then choose the schema where your tables are and then select your tables. For me my schema is assignment and my tables are Customers, inventory, products and sales.
Click Load to load your tables.

Loading tables in PowerBI and creating relationships
After loading the tables, PowerBI automatically detects the relationships between the tables based on shared columns.
However it is necessary to verify these relationships before working on the data to ensure accuracy.

For my case, I have four tables;

  • Customers table: stores customer information
  • Products table: contains products details
  • Sales table: records sales transactions
  • Inventory table: tracks stocks levels

To create relationship between tables in PowerBI;
Launch your PowerBI desktop if you haven’t.
Go to the Modelling tab in your PowerBI and select Manage Relationships.

Select manage Relationships

Check the relationships if they are okay and select the relationship you want to edit if any then click edit.

Edit relationships

Edit the relationship appropriately then Save.

Edit relationship

Proper relationships in PowerBI are important because they allow analysts to:

  • Join tables correctly.
  • Aggregate data accurately.
  • Enable filtering across visuals.
  • Build meaningful dashboards.

Importance of SQL skills for PowerBI analysts
While PowerBI is a powerful visualization tool, SQL remains a critical skill for any data analysts.
SQL helps analysts to:

  • Retrieve data efficiently from databases
  • Filter datasets using conditions
  • Perform aggregations
  • Join multiple tables
  • Prepare clean datasets before importing into PowerBI

In real world scenarios, analysts rarely use raw data directly. Instead they Use SQL to extract and shape data, Load the data into PowerBI then they build dashboards and generate insights from the data.

Conclusion
PowerBI and SQL databases together form a powerful combination for data analysis and business intelligence.
SQL databases provide a reliable and structured way to store and manage data whereas PowerBI transforms that data into clear and interactive insights that support data driven decision making.
Being able to connect Power BI and SQL together gives analysts a competitive advantage and organizations that leverage on this integration make smarter, faster and more informed decisions, which translate to better outcomes and sustained growth.

How to Accept USDC Payments in Node.js Using Web3indo (Stripe-Style Crypto Checkout)

How to Accept USDC Payments in Node.js Using Web3indo (Stripe-Style Crypto Checkout)

Accepting crypto payments in backend applications is still harder than it should be.

If you try to build it yourself, you usually need to:

– generate deposit wallets

– monitor blockchain transfers

– confirm ERC-20 events

– manage RPC reliability

– send webhook notifications

– sweep funds safely to treasury wallets

This quickly turns into infrastructure work instead of product work.

So I built Web3indo — a crypto payment infrastructure API designed for developers.

This article shows how to accept USDC payments in a Node.js backend using a Stripe-style workflow.

What Web3indo does

Web3indo provides:

– per-invoice deposit addresses

– automatic ERC-20 payment detection

– signed webhook delivery

– automatic treasury sweep

– project-scoped API keys

Currently supported:

Ethereum (ETH)

USDC

USDT

Step 1 — Create an API key

After creating a project inside the dashboard:

https://web3indo.com

generate a project-scoped API key.

Each project gets its own isolated API credentials.

Step 2 — Create an invoice

Example Node.js request:

import fetch from "node-fetch";

const response = await fetch("https://api.web3indo.com/v1/invoices", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-api-key": "YOUR_API_KEY"
  },
  body: JSON.stringify({
    chain: "sepolia",
    token: "USDC",
    amount: "0.01"
  })
});

const data = await response.json();

console.log(data);

Response includes:

– invoice ID

– deposit address

– payment status

– block tracking info

Step 3 — Wait for blockchain confirmation

Web3indo monitors ERC-20 transfer events automatically.

Invoice status transitions:

pending → paid

Step 4 — Receive webhook notification

Example webhook payload:

{
  "type": "invoice.paid",
  "invoiceId": "abc123",
  "chain": "sepolia",
  "token": "USDC",
  "amount": "0.01"
}

This allows your backend to react instantly:

unlock features

activate subscription

confirm order

Step 5 — Automatic treasury sweep

After payment confirmation:

funds are automatically transferred to your treasury wallet.

No manual wallet management required.

Supported workflow

Typical integration looks like:

create project

generate API key

create invoice

wait for payment

receive webhook

funds swept automatically

Example use cases

Web3indo works well for:

SaaS subscriptions

digital product checkout

developer tools billing

API usage payments

automation platforms

Why I built Web3indo

Most crypto payment integrations today still require managing wallets manually or running custom blockchain listeners.

Web3indo abstracts that complexity into a simple developer API.

Try it here

Landing page:

https://web3indo.com

Demo video:

https://youtu.be/Z83AlNK2Gxw

Feedback welcome

I’m actively improving Web3indo and would love feedback from other developers building crypto-enabled products.

7 Mac Apps for Developers Obsessed With Performance and Optimization in 2026

If you’re the kind of developer who watches Activity Monitor more than Netflix, profiles everything before shipping, and gets genuinely excited when a build shaves off 200ms — this list is for you.

These are the Mac apps I keep running because they help me stay fast, lean, and aware of where resources are going. No bloated suites, no Electron memory hogs. Just tools that respect your machine as much as you do.

1. Warp — A Terminal That Actually Keeps Up

Warp is a Rust-based terminal that feels like it was built by someone who profiled Bash startup times for fun. It launches instantly, renders output without lag, and has built-in AI command search so you spend less time in man pages. The block-based output model means you can copy, share, and scroll through command results without the usual terminal jank. If your terminal is the first thing you open every morning, Warp makes that moment painless.

2. Raycast — Spotlight Replacement With Zero Overhead

Raycast replaces Spotlight with something actually useful for developers. Clipboard history, snippet expansion, window management, and custom scripts — all from one keystroke. It’s native Swift, so it launches in under 100ms and never balloons in memory the way Electron-based launchers do. For performance-obsessed devs, the fact that your launcher itself is fast matters more than you’d think.

3. CleanShot X — Screenshot Tool That Doesn’t Waste Your Time

CleanShot X handles screenshots, screen recordings, and annotations without the clunkiness of macOS’s built-in tools. Scrolling capture, pinned screenshots that float on screen, OCR built in — it does a lot without ever feeling heavy. When you’re documenting a performance regression or sharing flamegraph snippets with your team, CleanShot gets out of the way and just works.

4. TokenBar — Know Exactly What Your LLM Calls Cost

TokenBar sits in your menu bar and tracks token usage across every LLM API call in real time. If you’re running AI-assisted workflows — Copilot, Claude, local models — token consumption is a performance metric you should be watching. TokenBar shows you input/output tokens, cost per call, and running totals without you opening a single dashboard. It’s $5 lifetime, native macOS, and uses almost no resources. For developers who optimize everything else, ignoring API costs is a blind spot this fixes.

5. Homebrew — The Foundation of Every Optimized Mac

Homebrew barely needs an introduction, but it belongs on any performance-focused list because it’s how you keep everything else running correctly. Managing dependencies, switching between tool versions, cleaning up stale packages — Homebrew does it quietly and reliably. Run brew cleanup and brew doctor regularly and your dev environment stays lean. It’s the unglamorous bedrock that makes the rest of your stack possible.

6. Monk Mode — Block the Feeds, Not the Apps

Monk Mode takes a different approach to focus: instead of blocking entire apps, it blocks the algorithmic feeds inside them. Twitter timeline gone, YouTube recommendations gone, Reddit front page gone — but you can still use search, DMs, and direct links. For performance-obsessed developers, the biggest bottleneck isn’t your CPU — it’s your attention. Monk Mode eliminates the scroll reflex without nuking your access to useful parts of the web. $15 lifetime, native Mac, negligible resource usage.

7. Numi — Calculator That Thinks Like a Developer

Numi is a text-based calculator that handles unit conversions, time zone math, percentages, and variables in a notepad-like interface. When you’re estimating request throughput, converting between MB and MiB, or calculating how many API calls fit in your budget, Numi handles it faster than opening a REPL. It’s native, lightweight, and does one thing extremely well — which is exactly the philosophy performance-minded devs respect.

The Common Thread

Every app on this list is either native macOS or built with performance-conscious tech (Rust, Swift). None of them hog memory or spin up background processes you didn’t ask for. If you care about how your code performs, you should care equally about how your tools perform.

The best developer setup isn’t the one with the most apps — it’s the one where every app earns its place in your dock.

What performance-focused tools are in your Mac setup? Drop them in the comments — always looking for tools that respect system resources.

SJF4J vs Jayway JsonPath: Up to 7x Faster in Java Benchmarks

We benchmarked JSONPath in Java.

SJF4J: https://github.com/sjf4j-projects/sjf4j

Jayway JsonPath: https://github.com/json-path/JsonPath

Result:

SJF4J is up to 7x faster.

Here’s what we found.

Benchmark Setup

We built a JMH benchmark suite comparing:

  • SJF4J
  • Jayway JsonPath

Across three workload shapes:

  • Path compilation
  • Query on shared JsonNode
  • Query on Map/List object graphs

JMH config:

  • 5 × 300ms warmup
  • 8 × 300ms measurement
  • 2 forks
  • 1 thread

Expressions used:

  • $.store.book[1].price
  • $.store.bicycle.color
  • $.store.book[*].author
  • $..price
  • $.store.book[?(@.price > 10)].title
  • $.store.book[0,2].title

Headline Results

Geometric mean (lower is better):

Benchmark group SJF4J Jayway Result
compile 97 ns/op 125 ns/op SJF4J 1.28x faster
query_definite 100 ns/op 237 ns/op SJF4J 2.38x faster
query_indefinite 656 ns/op 1294 ns/op SJF4J 1.97x faster
query_map_list_definite 31 ns/op 200 ns/op SJF4J 6.38x faster
query_map_list_indefinite 280 ns/op 1301 ns/op SJF4J 4.65x faster

TL;DR

  • ~2x faster on typical queries
  • up to 7x faster on Map/List
  • faster compilation as well

1) Query on Shared JsonNode

This is the fairest comparison: both libraries query the same parsed tree.

Definite paths

Expression SJF4J Jayway Result
$.store.book[1].price 111 ns/op 260 ns/op 2.35x faster
$.store.bicycle.color 90 ns/op 216 ns/op 2.40x faster

Indefinite paths

Expression SJF4J Jayway Result
$.store.book[*].author 342 ns/op 910 ns/op 2.66x faster
$..price 1736 ns/op 3877 ns/op 2.23x faster
$.store.book[?(@.price > 10)].title 1292 ns/op 1660 ns/op 1.29x faster
$.store.book[0,2].title 242 ns/op 478 ns/op 1.98x faster

Conclusion:

SJF4J is consistently faster as a JSONPath engine.

🚀 2) Query on Map/List Object Graphs

This is where the gap becomes much larger.

Both libraries support Map/List style object graphs, so this is still a fair comparison.

Definite paths

Expression SJF4J Jayway Result
$.store.book[1].price 37 ns/op 280 ns/op 7.50x faster
$.store.bicycle.color 26 ns/op 142 ns/op 5.42x faster

Indefinite paths

Expression SJF4J Jayway Result
$.store.book[*].author 135 ns/op 1045 ns/op 7.73x faster
$..price 543 ns/op 2495 ns/op 4.59x faster
$.store.book[?(@.price > 10)].title 823 ns/op 1836 ns/op 2.23x faster
$.store.book[0,2].title 102 ns/op 598 ns/op 5.89x faster

Takeaway:

If your application already works on Map/List object graphs,

this is the strongest result in the entire benchmark.

🔥 3) Native Object Graphs: POJO / JOJO

This is where SJF4J goes beyond traditional JSONPath libraries.

Instead of forcing everything into a JSON AST, SJF4J runs directly on:

  • Map / List
  • JOJO (JSON Object + Java Object hybrid)
  • POJO

Jayway does not support this model natively.

So here we compare SJF4J across different object representations.

Geometric mean

Benchmark group Map/List JOJO POJO
definite 31 ns/op 43 ns/op 94 ns/op
indefinite 270 ns/op 373 ns/op 554 ns/op

Representative results

Expression Map/List JOJO POJO
$.store.book[1].price 37 ns/op 46 ns/op 102 ns/op
$.store.bicycle.color 26 ns/op 40 ns/op 87 ns/op
$.store.book[*].author 131 ns/op 196 ns/op 374 ns/op
$..price 496 ns/op 914 ns/op 955 ns/op
$.store.book[?(@.price > 10)].title 807 ns/op 876 ns/op 1100 ns/op

What this shows:

  • Map/List is fastest (no abstraction)
  • JOJO adds minimal overhead
  • POJO is slower, but still practical

The key insight:

You can run JSONPath directly on your domain model.

No conversion. No intermediate tree.

What This Means in Practice

Instead of:

POJO → JsonNode → JSONPath → result

You can do:

POJO → JSONPath → result

This removes an entire layer from your data processing pipeline.

Why Is SJF4J Faster?

The performance difference mainly comes from:

  • no forced conversion into a dedicated AST
  • direct execution on native Java object graphs
  • lower abstraction overhead in path evaluation

In short:

SJF4J avoids work that other libraries must do first.

Bottom Line

Jayway is fine for basic JSONPath usage.

But if performance matters, especially on Map/List or POJO object graphs:

SJF4J is simply faster.

Links

GitHub: https://github.com/sjf4j-projects/sjf4j

Benchmark source: JsonPathCompareBenchmark.java

I Built an Andean Medicinal Plant Identifier That Uses Traditional Knowledge + Modern Data

Traditional medicine in the Andes has been practiced for thousands of years. But when I tried to find a simple tool that could tell you “what is this plant used for?” with actual scientific backing — nothing existed. So I built one.

The Problem

There are over 28,000 species of vascular plants in the tropical Andes, and indigenous communities have documented uses for thousands of them. But this knowledge lives in three disconnected worlds:

  1. Ethnobotanical databases (scattered across academic papers)
  2. Clinical trial registries (PubMed, ClinicalTrials.gov)
  3. Traditional healer knowledge (oral tradition, local pharmacopeias)

A pharmacist in Lima who wants to know about uña de gato (cat’s claw) has to cross-reference multiple sources manually. A patient asking “can I take maca with my blood pressure medication?” gets no answer from Google.

What I Built

An identifier and reference tool covering 250+ Andean and South American medicinal plants. Each entry includes:

  • Taxonomy: scientific name, family, common names in Spanish/Quechua/Aymara
  • Traditional uses: documented ethnobotanical applications
  • Evidence level: rated 1-5 based on available clinical evidence
  • Drug interactions: known contraindications with pharmaceuticals
  • Dosage ranges: from traditional preparation to standardized extracts

The whole thing runs as a static site — no backend, no database calls. All data is compiled at build time into a single JSON payload.

The Technical Approach

Data Collection

I started with three data sources:

  1. WHO monographs on medicinal plants — 150+ plants with formal assessments
  2. PubMed abstracts — searched "[plant name] AND (clinical trial OR systematic review)" for each plant
  3. Regional pharmacopeias — Peru’s DIGEMID list, Colombia’s Vademecum de Plantas Medicinales

For drug interactions, I cross-referenced the Natural Medicines database interaction dataset I’d built previously (592 documented interactions).

Architecture

data/plants.json (250 entries)
  → build script (Node.js)
    → site/plantas/[slug]/index.html (one page per plant)
    → site/api/plants.json (searchable index)
    → site/herramientas/plantas/ (search UI)

Each plant page is pre-rendered HTML with JSON-LD structured data:

{
  "@type": "MedicalEntity",
  "name": "Uncaria tomentosa",
  "alternateName": ["Uña de gato", "Cat's claw"],
  "relevantSpecialty": "Phytotherapy",
  "study": [
    {
      "@type": "MedicalStudy",
      "description": "Anti-inflammatory activity in rheumatoid arthritis",
      "studySubject": { "@type": "MedicalCondition", "name": "Arthritis" }
    }
  ]
}

Search

Client-side fuzzy search using a simple trigram index. No external dependencies.

function search(query) {
  const trigrams = getTrigrams(query.toLowerCase());
  return plants
    .map(p => ({
      plant: p,
      score: trigrams.reduce((s, t) =>
        s + (p.searchIndex.includes(t) ? 1 : 0), 0) / trigrams.length
    }))
    .filter(r => r.score > 0.3)
    .sort((a, b) => b.score - a.score);
}

Users can search by common name (in Spanish, Quechua, or English), scientific name, or health condition.

What Surprised Me

1. Evidence quality varies wildly

Maca (Lepidium meyenii) has 12 systematic reviews. Chiric sanango (Brunfelsia grandiflora) has exactly zero clinical trials but centuries of documented traditional use. How do you rate evidence for a plant that’s been used since the Inca empire but never studied in a double-blind trial?

I ended up with a 5-tier system:

  • Level 5: Multiple systematic reviews/meta-analyses
  • Level 4: At least 1 RCT with >100 participants
  • Level 3: Small clinical trials or strong animal studies
  • Level 2: Case reports or pharmacological analysis only
  • Level 1: Traditional use documented, no clinical research

Most Andean plants sit at Level 1-2. That doesn’t mean they don’t work — it means they haven’t been studied yet.

2. Drug interaction data is critically incomplete

Of the 250 plants in the database, only 47 have documented drug interactions. That doesn’t mean the other 203 are safe — it means nobody checked. This is especially dangerous for plants like ayahuasca (contains MAOIs) or sangre de drago (potent anti-inflammatory).

3. Naming is chaos

One plant can have 15+ common names depending on the region. “Muña” in Peru is Minthostachys mollis, but in some areas of Bolivia it refers to a completely different species. Scientific names are the only reliable identifier — but patients don’t use scientific names.

I solved this with an alias system that maps all known common names to the canonical scientific name, with region tags.

Performance

The entire site weighs 180KB gzipped (including all 250 plant entries). First Contentful Paint under 1 second. No JavaScript frameworks — just vanilla JS for the search.

Lighthouse scores: Performance 98, Accessibility 96, Best Practices 100, SEO 100.

What’s Next

I’m working on adding:

  • Interaction cross-checker: “I take metformin and want to try berberine — is that safe?” (builds on the interaction database)
  • Photo identification: upload a plant photo, get possible matches (this is hard and will probably be a separate project)
  • Quechua language support: plant names and basic descriptions in Quechua

The tool is live at Botánica Andina if you want to try it. The plant data is open — you can grab the JSON and build on it.

If you work with ethnobotanical data or health APIs, I’d love to hear how you handle evidence grading for traditional medicines. It’s a genuinely unsolved problem.

Can AI Exit Vim?

Technical Analysis: Can AI Exit Vim?

The question of whether AI can exit Vim has sparked debate and curiosity among developers and AI enthusiasts alike. To address this, we’ll dive into the technical aspects of the problem, exploring the challenges and possibilities of using AI to navigate and exit the Vim text editor.

Understanding Vim

Vim is a modal text editor with a unique command set and keyboard shortcuts. Its modal nature means that the editor exists in one of several modes, such as insert, normal, or command mode, each with its own set of available commands. This modal design is a key aspect of Vim’s efficiency and flexibility but also presents a challenge for AI systems attempting to navigate and exit the editor.

AI Interaction with Vim

To interact with Vim, an AI system would need to understand the editor’s command set, modal structure, and keyboard shortcuts. This requires a deep understanding of the editor’s internal state and the ability to parse and execute commands. The AI would need to be able to:

  1. Parse Vim commands: Understand the syntax and semantics of Vim commands, including motion commands, editing commands, and ex commands.
  2. Understand modal state: Recognize the current modal state of the editor and adjust its behavior accordingly.
  3. Execute commands: Send keystrokes or commands to Vim to perform actions such as inserting text, deleting characters, or navigating the editor.

Challenges for AI

Several challenges make it difficult for AI to exit Vim:

  1. Modal ambiguity: The AI may struggle to determine the current modal state of the editor, leading to incorrect command execution or misinterpretation of user input.
  2. Command complexity: Vim’s command set is vast and nuanced, making it difficult for AI to understand the context and intent behind user input.
  3. Keyboard shortcut overload: Vim’s extensive use of keyboard shortcuts can overwhelm an AI system, making it challenging to determine the correct sequence of keystrokes to exit the editor.
  4. Lack of visual feedback: Unlike graphical user interfaces, Vim provides limited visual feedback, making it harder for AI to understand the editor’s state and respond accordingly.

Possible Approaches

To overcome these challenges, AI systems could employ various strategies:

  1. Reinforcement learning: Train an AI model using reinforcement learning to learn the optimal sequence of keystrokes to exit Vim through trial and error.
  2. Vim script parsing: Develop an AI system that can parse and understand Vim scripts, allowing it to execute commands and navigate the editor.
  3. Modal state tracking: Implement a system that tracks the modal state of the editor, enabling the AI to adjust its behavior and execute commands accordingly.
  4. Hybrid approach: Combine multiple approaches, such as using reinforcement learning to learn keystroke sequences and parsing Vim scripts to understand the editor’s internal state.

Conclusion is not needed, however, the analysis leads to the following results

The Can AI Exit Vim? challenge highlights the complexities of interacting with a modal text editor like Vim. While it is theoretically possible to develop an AI system that can exit Vim, the challenges and nuances of the editor’s design make it a difficult task. By understanding the technical aspects of the problem and exploring possible approaches, we can better appreciate the intricacies of human-computer interaction and the limitations of current AI systems. Ultimately, the development of an AI that can exit Vim would require significant advances in natural language processing, reinforcement learning, and human-computer interaction.

Omega Hydra Intelligence
🔗 Access Full Analysis & Support

Beyond the Hype: Building a Practical AI Memory System with Vector Databases

Your Agent Can Think. Let’s Teach It to Remember.

The recent surge in AI agent development has revealed a critical bottleneck: memory. As one popular article this week poignantly stated, “your agent can think. it can’t remember.” We’re building remarkably intelligent systems that process each interaction as a blank slate, forgetting crucial context from previous conversations, decisions, and learned information. This isn’t just a theoretical limitation—it’s what makes AI assistants give contradictory advice, chatbots restart conversations endlessly, and analytical tools fail to build on prior insights.

The solution lies in giving our AI systems a practical, scalable memory. Not by dumping entire conversation histories into prompts (which quickly hits token limits and costs), but by implementing intelligent memory retrieval. In this guide, we’ll move beyond the hype and build a working memory system using vector databases—the same technology powering sophisticated AI applications today.

Why Traditional Approaches Fail

Before we build our solution, let’s examine why common approaches fall short:

1. Full History Injection

# The problematic approach
conversation_history = get_entire_chat_history(user_id)  # Could be 50K tokens!
prompt = f"{conversation_history}nnUser: {new_message}nAI:"
response = call_llm(prompt)  # Expensive and slow

This approach quickly becomes unsustainable as context windows fill up and API costs skyrocket.

2. Simple Windowed Memory

# Only remembering the last N messages
recent_messages = chat_history[-10:]  # What about important info from message #11?

This loses crucial long-term context and important details from earlier interactions.

3. Manual Summary Systems

# Periodically summarizing conversations
if len(chat_history) > 20:
    summary = create_summary(chat_history)
    chat_history = [summary] + chat_history[-5:]

While better, this loses granular details and requires deciding what to summarize and when.

The Vector Database Solution

Vector databases solve this by storing information as numerical vectors (embeddings) that capture semantic meaning. When we need to remember something, we don’t search by keywords—we search by meaning.

How It Works

  1. Convert text to vectors using embedding models
  2. Store vectors with metadata in a specialized database
  3. Retrieve relevant memories by finding similar vectors
  4. Inject only relevant context into the LLM prompt

This approach is efficient, scalable, and semantically intelligent.

Building Our Memory System

Let’s implement a complete memory system using Python, OpenAI embeddings, and ChromaDB (an open-source vector database).

Step 1: Setting Up Our Environment

# requirements.txt
# openai
# chromadb
# python-dotenv

import os
import chromadb
from chromadb.config import Settings
from openai import OpenAI
from datetime import datetime
import json

# Initialize clients
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
chroma_client = chromadb.Client(Settings(
    chroma_db_impl="duckdb+parquet",
    persist_directory="./chroma_db"
))

Step 2: Creating the Memory Store

class AIMemorySystem:
    def __init__(self, user_id, collection_name="ai_memories"):
        self.user_id = user_id
        self.collection_name = f"{collection_name}_{user_id}"

        # Get or create collection
        self.collection = chroma_client.get_or_create_collection(
            name=self.collection_name,
            metadata={"hnsw:space": "cosine"}  # Cosine similarity for text
        )

    def _get_embedding(self, text):
        """Convert text to vector embedding"""
        response = client.embeddings.create(
            model="text-embedding-3-small",
            input=text
        )
        return response.data[0].embedding

    def store_memory(self, text, metadata=None):
        """Store a new memory with automatic embedding"""
        embedding = self._get_embedding(text)

        # Prepare metadata
        memory_metadata = {
            "timestamp": datetime.now().isoformat(),
            "user_id": self.user_id,
            "text": text,
            **metadata if metadata else {}
        }

        # Generate unique ID
        memory_id = f"memory_{datetime.now().timestamp()}"

        # Store in vector database
        self.collection.add(
            embeddings=[embedding],
            documents=[text],
            metadatas=[memory_metadata],
            ids=[memory_id]
        )

        return memory_id

Step 3: Intelligent Memory Retrieval

class AIMemorySystem(AIMemorySystem):
    def retrieve_relevant_memories(self, query, n_results=5, threshold=0.7):
        """Find memories relevant to the current context"""
        query_embedding = self._get_embedding(query)

        # Search for similar memories
        results = self.collection.query(
            query_embeddings=[query_embedding],
            n_results=n_results,
            include=["documents", "metadatas", "distances"]
        )

        # Filter by similarity threshold and format results
        relevant_memories = []
        for i, distance in enumerate(results["distances"][0]):
            if distance < threshold:  # Lower distance = more similar
                memory = {
                    "text": results["documents"][0][i],
                    "metadata": results["metadatas"][0][i],
                    "similarity": 1 - distance  # Convert to similarity score
                }
                relevant_memories.append(memory)

        # Sort by relevance
        relevant_memories.sort(key=lambda x: x["similarity"], reverse=True)
        return relevant_memories

    def get_context_for_prompt(self, current_query, max_tokens=1000):
        """Build context string from relevant memories"""
        memories = self.retrieve_relevant_memories(current_query)

        context_parts = []
        token_count = 0

        for memory in memories:
            memory_text = f"Previous context: {memory['text']}n"
            estimated_tokens = len(memory_text) // 4  # Rough estimate

            if token_count + estimated_tokens > max_tokens:
                break

            context_parts.append(memory_text)
            token_count += estimated_tokens

        return "n".join(context_parts)

Step 4: Integrating with an LLM

class AIAgentWithMemory:
    def __init__(self, user_id):
        self.memory = AIMemorySystem(user_id)
        self.conversation_buffer = []  # Short-term buffer

    def process_message(self, user_message):
        # Get relevant memories for context
        context = self.memory.get_context_for_prompt(user_message)

        # Store this interaction as a memory
        self.memory.store_memory(
            text=f"User: {user_message}",
            metadata={"type": "user_message"}
        )

        # Build the enhanced prompt
        prompt = f"""You are an AI assistant with access to past conversation context.

Relevant past context:
{context}

Current conversation:
{self._format_recent_conversation()}

User: {user_message}

Assistant:"""

        # Get response from LLM
        response = self._call_llm(prompt)

        # Store the response as memory
        self.memory.store_memory(
            text=f"Assistant: {response}",
            metadata={"type": "assistant_response"}
        )

        # Update conversation buffer
        self.conversation_buffer.append(f"User: {user_message}")
        self.conversation_buffer.append(f"Assistant: {response}")
        self.conversation_buffer = self.conversation_buffer[-6:]  # Keep last 3 exchanges

        return response

    def _format_recent_conversation(self):
        return "n".join(self.conversation_buffer[-4:])  # Last 2 exchanges

    def _call_llm(self, prompt):
        response = client.chat.completions.create(
            model="gpt-4-turbo-preview",
            messages=[{"role": "user", "content": prompt}],
            max_tokens=500
        )
        return response.choices[0].message.content

Advanced Memory Techniques

Memory Prioritization and Decay

Not all memories are equally important. Let’s implement a sophisticated memory management system:

class EnhancedMemorySystem(AIMemorySystem):
    def __init__(self, user_id, collection_name="enhanced_memories"):
        super().__init__(user_id, collection_name)

    def store_memory_with_importance(self, text, importance_score=1.0, 
                                     memory_type="conversation", tags=None):
        """Store memory with importance scoring and categorization"""

        metadata = {
            "importance": importance_score,
            "type": memory_type,
            "tags": tags or [],
            "access_count": 0,
            "last_accessed": datetime.now().isoformat(),
            "created_at": datetime.now().isoformat()
        }

        return self.store_memory(text, metadata)

    def retrieve_with_importance_weighting(self, query, n_results=5):
        """Retrieve memories weighted by importance and recency"""
        results = self.retrieve_relevant_memories(query, n_results * 2)

        # Apply weighting
        for memory in results:
            importance = memory["metadata"].get("importance", 1.0)
            last_accessed = datetime.fromisoformat(
                memory["metadata"].get("last_accessed", 
                                     memory["metadata"]["timestamp"])
            )

            # Calculate age in days
            age_days = (datetime.now() - last_accessed).days

            # Weight: importance * recency_factor * similarity
            recency_factor = max(0.1, 1.0 - (age_days * 0.01))
            memory["weighted_score"] = (
                importance * 
                recency_factor * 
                memory["similarity"]
            )

            # Update access metadata
            memory["metadata"]["access_count"] += 1
            memory["metadata"]["last_accessed"] = datetime.now().isoformat()

        # Sort by weighted score and return top results
        results.sort(key=lambda x: x["weighted_score"], reverse=True)
        return results[:n_results]

Memory Compression and Summarization

For long-running conversations, we need to compress old memories:

class CompressingMemorySystem(EnhancedMemorySystem):
    def compress_old_memories(self, max_memories=1000, compression_threshold=0.9):
        """Compress similar old memories into summaries"""

        # Get all memories sorted by age
        all_memories = self.collection.get()

        if len(all_memories["ids"]) <= max_memories:
            return

        # Find clusters of similar old memories
        old_memories = self._get_old_memories()

        # Group similar memories (simplified clustering)
        clusters = self._cluster_similar_memories(old_memories, compression_threshold)

        # Compress each cluster
        for cluster in clusters:
            if len(cluster) > 3:  # Only compress significant clusters
                summary = self._create_cluster_summary(cluster)

                # Store summary
                self.store_memory_with_importance(
                    text=f"Summary of related memories: {summary}",
                    importance=sum(m["metadata"].get("importance", 1.0) 
                                  for m in cluster) / len(cluster),
                    memory_type="summary",
                    tags=["compressed"]
                )

                # Remove original memories (in production, you might archive instead)
                self.collection.delete(ids=[m["id"] for m in cluster])

Putting It All Together: A Complete Example

# Initialize our enhanced AI agent
agent = AIAgentWithMemory("user_123")

# Simulate a conversation over time
conversations = [
    "I'm planning a trip to Japan next spring.",
    "I want to visit Tokyo and Kyoto.",
    "What are some good temples in Kyoto?",
    "Also, I'm allergic to shellfish - any food tips?",
    "What was that temple you recommended in Kyoto again?",
    "And remind me about the food restrictions we discussed."
]

print("=== AI Agent with Memory Demo ===n")
for i, message in enumerate(conversations):
    print(f"User: {message}")
    response = agent.process_message(message)
    print(f"Assistant: {response[:100]}...")  # Truncate for display
    print(f"--- Memory Context Used: {len(agent.memory.get_context_for_prompt(message))} chars ---n")

    if i == 3:  # Simulate time passing
        print("n[Time passes... user returns days later]n")

Best Practices and Considerations

  1. Privacy First: Always encrypt sensitive user data and consider on-premise deployment for private data
  2. Cost Management: Cache embeddings and implement usage limits
  3. Memory Validation: Periodically validate that retrieved memories remain relevant
  4. User Control: Provide interfaces for users to view, edit, and delete their memories
  5. Hybrid Approaches: Combine vector search with traditional database queries for factual data

The Future of AI Memory

The system we’ve built is just the beginning. Future advancements will likely include:

  • Hierarchical memory structures for different timescales
  • Cross-modal memory (text, images, audio in one system)
  • Predictive memory retrieval (anticipating what you’ll need)
  • Federated learning for privacy-preserving shared memory

Start Building Smarter AI Today

Memory isn’t just a nice-to-have feature—it’s what transforms AI from a clever parlor trick into a truly useful tool. By implementing a vector-based memory system, you’re not just solving the “can’t remember” problem; you’re building AI that learns, adapts, and grows with your users.

Your Challenge: Take the code from this guide and extend it with one new feature this week. It could be memory expiration, emotional tone tracking, or cross-user memory sharing (with permission). Share what you build—the best solutions often come from practical experimentation.

Remember: The AI that remembers is the AI that matters. What will yours remember?

Want to dive deeper? Check out the complete code examples on [GitHub] and join the discussion about AI memory systems in the comments below. What memory features are you building?