diff --git a/CNAME b/CNAME index 8b137891791f..af154ddf7743 100644 --- a/CNAME +++ b/CNAME @@ -1 +1 @@ - +www.cubometrics.ca \ No newline at end of file diff --git a/_config.yml b/_config.yml index d4916414195c..0d87aac9cdf7 100644 --- a/_config.yml +++ b/_config.yml @@ -3,13 +3,14 @@ # # Name of your site (displayed in the header) -name: Your Name +name: CuboMetrics # Short bio or description (displayed in the header) -description: Web Developer from Somewhere +description: Build Smarter, Query Better # URL of your avatar or profile pic (you could use your GitHub profile pic) -avatar: https://raw.githubusercontent.com/barryclark/jekyll-now/master/images/jekyll-logo.png +avatar: /images/cubometrics.png +#https://raw.githubusercontent.com/barryclark/jekyll-now/master/images/jekyll-logo.png # # Flags below are optional @@ -21,12 +22,12 @@ footer-links: email: facebook: flickr: - github: barryclark/jekyll-now + github: jorgerocha10 instagram: - linkedin: + linkedin: jorge-rocha-b863b831 pinterest: rss: # just type anything here for a working RSS icon - twitter: jekyllrb + twitter: stackoverflow: # your stackoverflow profile, e.g. "users/50476/bart-kiers" youtube: # channel/ or user/ googleplus: # anything in your profile username that comes after plus.google.com/ @@ -37,11 +38,11 @@ footer-links: disqus: # Enter your Google Analytics web tracking code (e.g. UA-2110908-2) to activate tracking -google_analytics: +google_analytics: G-C89XSHP9RZ # Your website URL (e.g. http://barryclark.github.io or http://www.barryclark.co) # Used for Sitemap.xml and your RSS feed -url: +url: cubometrics.ca # If you're hosting your site at a Project repository on GitHub pages # (http://yourusername.github.io/repository-name) diff --git a/_posts/2014-3-3-Hello-World.md b/_posts/2014-3-3-Hello-World.md deleted file mode 100644 index d4665b6d18e9..000000000000 --- a/_posts/2014-3-3-Hello-World.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -layout: post -title: You're up and running! ---- - -Next you can update your site name, avatar and other options using the _config.yml file in the root of your repository (shown below). - -![_config.yml]({{ site.baseurl }}/images/config.png) - -The easiest way to make your first post is to edit this one. Go into /_posts/ and update the Hello World markdown file. For more instructions head over to the [Jekyll Now repository](https://github.com/barryclark/jekyll-now) on GitHub. \ No newline at end of file diff --git a/_posts/2025-7-24-What-is-text-to-SQL.md b/_posts/2025-7-24-What-is-text-to-SQL.md new file mode 100644 index 000000000000..bd4ba7a92593 --- /dev/null +++ b/_posts/2025-7-24-What-is-text-to-SQL.md @@ -0,0 +1,131 @@ +--- +layout: post +title: What is Text-to-SQL? +--- + +I recently stumbled across a tool called [WrenAI](https://getwren.ai/) that claims you can query any database using plain English. I was a little skeptical at first, but after burning through my free credits, I realized they weren’t bluffing. It felt kind of magical to ask questions in natural language and watch it generate relevant SQL and charts in real time. + +As someone who works in data modeling and business intelligence, I know how critical a solid underlying model is. Your semantic and reporting layers are only as good as the foundation they sit on. So when a generative AI tool can consistently return useful SQL, readable tables, and insightful charts, you start to wonder if dashboards as we know them are about to be redefined. + +What really caught my attention, though, was how tools like this could help democratize access to data. You don’t need to know SQL or even how to use a BI tool. This abstraction layer means non-technical users can get answers on their own, often in seconds. For data teams, that means fewer repetitive ad-hoc requests like “Can you pull sales by region?” or “Oh wait, can you add distribution channel to that?” and more time spent on better modeling and optimization work. + +I was curious enough to dig into [WrenAI GitHub repo](https://github.com/Canner/WrenAI/tree/main) to see how it all worked behind the scenes. + +![_config.yml]({{ site.baseurl }}/images/how_wrenai_works.png) + +At first glance, it looks a bit overwhelming. But the point of this post, and the ones to follow, is to unpack that complexity and explore how these tools actually work under the hood. + +Let’s start by breaking down the architecture diagram above. + +![_config.yml]({{ site.baseurl }}/images/diagram_breakdown.png) + +--- + +## 1. Connect (Getting the Data In) + +### What it does: + +This is where a Text-To-SQL system (e.g. WrenAI) connects to your actual data - databases, data warehouses, and more. + +- **Connect**: Add a data source (like Synapse Analytics, Snowflake, Postgres, etc.) +- **Reads Metadata**: Grabs table names, column types, and other schema details + +Think of it like introducing the Text-To-SQL system to your data for the first time. + +--- + +## 2. Modeling (Defining the Semantics) + +### What it does: + +Before WrenAI can do anything useful, you help it understand your data by defining semantics - descriptions, meanings, and relationships. + +- You describe what fields mean (e.g., `order_total` = total sales). +- It creates a **semantic model** to guide its understanding. + +This is where you teach the system your organization’s vocabulary. + +--- + +## 3. Indexing (Organizing the Knowledge) + +### What it does: + +WrenAI processes your schema, metadata, and semantics, and stores them in a **vector database** for fast and relevant retrieval. + +- **Index Processing**: Converts the model into vectorized data +- **Vector Database**: Makes that knowledge easily searchable + +It’s like building a memory of your data that the AI can quickly reference. + +--- + +## 4. Retrieval (Finding the Right Context) + +### What it does: + +When a user asks a question, WrenAI looks through its vector memory to find the most relevant context - like tables, fields, and relationships. + +- **Retrieval Service**: Matches the question to the right metadata + +It’s like checking your notes before answering an exam question. + +--- + +## 5. Prompting & Generation (Creating the SQL) + +### What it does: + +Now WrenAI combines the user’s question with relevant context and forms a prompt for an LLM (like GPT-4, Claude, Gemini, you name it, the frontier models will always return better results) to generate SQL. + +- **Prompt**: Merges question + metadata +- **LLM**: Generates the SQL query +- **Output Processing**: Validates the query to make sure it works + +This is the smart part — translating natural language into working SQL. + +--- + +## 6. Wren UI (The User Interface) + +### What it does: + +This is what users see. You type your question into a chat-like interface, and WrenAI handles the rest. + +- Sends your question to the backend +- Returns SQL results and visualizations + +It’s the conversation window where data questions become answers. + +--- + +## 7. Wren Engine (Query Execution Layer) + +### What it does: + +Once the SQL is ready, it has to run somewhere. That’s the job of the Wren Engine. + +- **Core**: Executes the SQL against the connected data source +- **Metastore**: Stores schema and semantic models for reuse + +This is the engine room — quietly powering the results behind the scenes. + +--- + +## Summary in Plain English + +> You connect your data → define what it means → WrenAI processes and remembers it → when someone asks a question, it finds the right context → sends it to an AI to generate SQL → runs the SQL → and gives the user an answer. + +--- + +## Wrapping Up + +Exploring WrenAI gave me a glimpse into what the future of analytics could look like: a world where anyone in a company can ask a question and get a useful answer instantly, without waiting days for a report or dashboard. Under the hood, it's a blend of old-school data modeling, modern indexing, and generative AI — all working together. + +What surprised me most is how much of this relies on concepts we already understand as data professionals: schemas, semantics, metadata, and SQL. The magic is in how they're combined. + +This is just the beginning. In upcoming posts, I’ll explore each piece in more depth, from building semantic models to how prompts are structured and validated, and share what I learn along the way. My goal is to better understand how to make data truly AI-ready, and I hope you’ll follow along. If you’re curious about this space or already exploring it yourself, let’s learn together. + +Cheers, + +Jorge Rocha diff --git a/_posts/2025-7-28-What-Is-RAG-and-Why-Does-It-Matter-for-Text-to-SQL?.md b/_posts/2025-7-28-What-Is-RAG-and-Why-Does-It-Matter-for-Text-to-SQL?.md new file mode 100644 index 000000000000..f6446bade9b5 --- /dev/null +++ b/_posts/2025-7-28-What-Is-RAG-and-Why-Does-It-Matter-for-Text-to-SQL?.md @@ -0,0 +1,290 @@ +--- + +layout: post + +title: What Is RAG and Why Does It Matter for Text-to-SQL? + +--- + +Trying to use LLMs to generate SQL without providing your schema and business rules is almost a waste of time. + +It generally hallucinates, making up table or column names that don’t exist. The LLM doesn’t know your business logic, your naming conventions, or your schema constraints. You can try stuffing your schema into every prompt, but that quickly becomes inefficient, brittle, and doesn’t scale. + +So what’s the alternative? + +--- + +## What Is RAG? + +**Retrieval-Augmented Generation (RAG)** is an AI architecture that combines traditional retrieval methods (like search engines or vector databases) with language models. Instead of relying solely on an LLM’s memory, RAG feeds it contextual data from your own knowledge base (like schemas, documentation, or logs) before generating a response. + +> “RAG (Retrieval-Augmented Generation) is an AI framework that combines the strengths of traditional information retrieval systems (such as search and databases) with the capabilities of generative large language models (LLMs). By combining your data and world knowledge with LLM language skills, grounded generation is more accurate, up-to-date, and relevant to your specific needs.” +> +> — [Google Cloud Docs](https://cloud.google.com/use-cases/retrieval-augmented-generation) + +![_config.yml]({{ site.baseurl }}/images/RAG_Basic_Standard.png) + +### Think of It Like a Librarian + +Imagine asking a librarian a question. Instead of guessing or making up answers, they know _exactly_ which books to reference, where to find them, and how to extract what’s useful for you. That’s what RAG does, it retrieves relevant, authoritative context for the LLM to use when answering. + +--- + +## Why RAG Matters for Text-to-SQL + +I’ve seen the difference firsthand. + +When I asked [WrenAI](https://getwren.ai/) a question, it didn’t just “guess” a SQL query, it retrieved my schema and used that in the prompt. The result? Queries that actually ran. + +### Benefits of using RAG for Text-to-SQL: + +- **Reduces hallucinations** by grounding output in real schema +- **Adapts to different schemas** without hardcoding or retraining +- **More accurate and relevant** queries tailored to your business context +- **Customization-ready** with no fine-tuning +- **Fresh information** from changing databases or schema evolution + +--- + +# How to Build a RAG System for SQL Generation Using LangChain and a Local Vector Database + +Let’s walk through how to build a **simple but functional RAG pipeline for Text-to-SQL**, using LangChain’s new `v0.2+` interface. + +--- + +## 🛠️ Environment Setup: Python Virtualenv + Dependencies + +Before diving into code, let’s make sure your environment is clean and ready. I’m using Python 3.12 for this exercise. + +### Step 1: Create a Virtual Environment + +```bash +python -m venv venv +# On Windows: +venv\Scripts\activate +``` + +### Step 2: Install Required Libraries + +```bash +pip install langchain langchain-openai langchain-community faiss-cpu +``` + +✅ `langchain-openai` provides OpenAI embeddings + models +✅ `langchain-community` includes community-maintained vector stores like FAISS +✅ `faiss-cpu` is for local semantic search + +--- + +## Step 3: Setup and Imports + +You’ll notice we're using both `langchain_openai` and `langchain_community`, which reflects the recent modular split in LangChain v0.2+. + +```python +from langchain_openai import OpenAIEmbeddings, OpenAI +from langchain_community.vectorstores import FAISS +from langchain.text_splitter import CharacterTextSplitter +from langchain.prompts import PromptTemplate +from langchain_core.runnables import RunnablePassthrough +import os + +# Set your OpenAI API key +os.environ["OPENAI_API_KEY"] = "sk-..." # Use env vars or secret manager in production +``` + +✅ Why this matters: We’re pulling in everything needed to load text data, chunk it, embed it, store it in a retriever, build a prompt, and finally generate SQL using OpenAI. + +--- + +## Step 4: Define Your Data Schema (Context) + +This is your **domain knowledge**. We simulate a simple database schema in plain text. + +```python +schema_docs = """ +Table: orders +Columns: order_id, customer_id, order_date, total_amount + +Table: customers +Columns: customer_id, name, email, region +""" +``` + +✅ Why this matters: RAG systems work best when the context is rich and specific. Here, we define the structure the AI will use to answer questions. + +--- + +## Step 5: Chunk the Text + +We split the schema into chunks. Even if the schema is small now, chunking is a good habit to support scalability. + +```python +splitter = CharacterTextSplitter(chunk_size=500, chunk_overlap=0) +docs = splitter.create_documents([schema_docs]) +``` + +✅ Why this matters: Large documents can confuse LLMs. Chunking ensures each piece stays within token limits and remains semantically focused. + +--- + +## Step 6: Embed and Store with FAISS + +We convert text chunks into vector embeddings using OpenAI and index them using FAISS, a fast, in-memory vector store. + +```python +embeddings = OpenAIEmbeddings() +vectorstore = FAISS.from_documents(docs, embeddings) +retriever = vectorstore.as_retriever() +``` + +✅ Why this matters: This is the “retrieval” in RAG. When a user asks a question, we use this retriever to find the most relevant schema chunks. + +--- + +## Step 7: Define the Prompt Template + +This is the **instruction format** you give the LLM. It tells the model: “Here’s the schema. Now answer this question by generating SQL.” + +```python +prompt = PromptTemplate( + input_variables=["context", "question"], + template=""" +You are an AI assistant that writes SQL queries based on a database schema. + +Schema: +{context} + +Question: +{question} + +SQL Query: +""" +) +``` + +✅ Why this matters: Prompt engineering is core to RAG. We combine context + question into a structured input the LLM can reason over. + +--- + +## Step 8: Create the LCEL Chain (LangChain Expression Language) + +Here we wire together the components into a pipeline: **Retriever ➝ Prompt ➝ LLM** + +```python +llm = OpenAI(temperature=0) + +retrieval_chain = ( + {"context": retriever, "question": RunnablePassthrough()} + | prompt + | llm +) +``` + +✅ Why this matters: LCEL makes your pipeline modular, inspectable, and easier to test. `RunnablePassthrough()` just forwards the raw question as-is. + +--- + +## Step 9: Ask a Question and Run the Chain + +Now you can send a natural language question to the pipeline and get back an AI-generated SQL query: + +```python +question = "What’s the total sales by region?" +response = retrieval_chain.invoke(question) + +print(response) +``` + +**Expected Output:** + +```sql +SELECT customers.region, SUM(orders.total_amount) AS total_sales +FROM orders +INNER JOIN customers ON orders.customer_id = customers.customer_id +GROUP BY customers.region; +``` + +**Full code:** + +```python +from langchain_openai import OpenAIEmbeddings, OpenAI +from langchain_community.vectorstores import FAISS +from langchain.text_splitter import CharacterTextSplitter +from langchain.prompts import PromptTemplate +from langchain_core.runnables import RunnablePassthrough +import os + +# Set your API key +os.environ["OPENAI_API_KEY"] = "YOUR-OPENAI-API-KEY" + +# Example schema as plain text +schema_docs = """ +Table: orders +Columns: order_id, customer_id, order_date, total_amount + +Table: customers +Columns: customer_id, name, email, region +""" + +# Step 1: Split schema into chunks +splitter = CharacterTextSplitter(chunk_size=500, chunk_overlap=0) +docs = splitter.create_documents([schema_docs]) + +# Step 2: Embed and create FAISS vector store +embeddings = OpenAIEmbeddings() +vectorstore = FAISS.from_documents(docs, embeddings) +retriever = vectorstore.as_retriever() + +# Step 3: Prompt template +prompt = PromptTemplate( + input_variables=["context", "question"], + template=""" +You are an AI assistant that writes SQL queries based on a database schema. + +Schema: +{context} + +Question: +{question} + +SQL Query: +""" +) + +# Step 4: Define components for LCEL chain +llm = OpenAI(temperature=0) + +# Create retriever -> prompt -> llm pipeline +retrieval_chain = ( + {"context": retriever, "question": RunnablePassthrough()} + | prompt + | llm +) + +# Step 5: Invoke the LCEL chain +question = "What’s the total sales by region?" +response = retrieval_chain.invoke(question) + +# Output result +print(response) +``` + +✅ Why this matters: This is the end-to-end RAG pipeline, no manual SQL needed, just your schema and a natural language question. + +--- + +## Summary: Why This Is RAG + +This system retrieves relevant schema text (**R**), augments the prompt with it (**A**), and uses a generative model to answer your question (**G**). That's **Retrieval-Augmented Generation**. + +--- + +## Final Thoughts + +This RAG-powered approach is very simple, but still close to how real-world analysts or engineers operate, they consult the schema, then write SQL. LLMs can now do the same. + +By bringing together semantic retrieval and generative reasoning, RAG systems give us the best of both worlds: precision and language flexibility. And as tools like [LangChain](https://python.langchain.com/) mature, building these pipelines is becoming not just possible, but practical. + +Cheers, + +Jorge Rocha diff --git a/_posts/2025-7-30-What-Is-Pydantic-and-Why-Should-You-Use-It.md b/_posts/2025-7-30-What-Is-Pydantic-and-Why-Should-You-Use-It.md new file mode 100644 index 000000000000..970e410195b0 --- /dev/null +++ b/_posts/2025-7-30-What-Is-Pydantic-and-Why-Should-You-Use-It.md @@ -0,0 +1,207 @@ +--- + +layout: post + +title: What Is Pydantic and Why Should You Use It? + +--- + +If you've been building Python applications, especially anything involving APIs or AI, you've probably run into a frustrating problem: bad data sneaking into your code. + +Maybe a function expected an integer but got a string. Maybe an API response came back with missing fields. Maybe you spent 30 minutes debugging only to realize a dictionary key was misspelled. These kinds of bugs are silent, annoying, and surprisingly common in Python. + +So how do you fix this? Meet **Pydantic**. + +--- + +## What Is Pydantic? + +**Pydantic** is a Python library for data validation using type hints. You define what your data _should_ look like using Python classes, and Pydantic makes sure the data actually matches, at runtime. + +> "Pydantic is the most widely used data validation library for Python. It uses Python type hints to validate data, serialize it, and generate JSON schemas, all with a clean, Pythonic API." +> +> — [Pydantic Docs](https://docs.pydantic.dev/latest/) + +### Think of It Like a Bouncer at a Club + +Imagine your function is a club, and the data coming in is a guest. Without Pydantic, anyone walks in, no ID check, no guest list. With Pydantic, there's a bouncer at the door: wrong type? Rejected. Missing field? Rejected. Everything checks out? Come on in. + +--- + +## A Simple Example + +Let's start with the basics. Here's how you'd define a simple data model with Pydantic: + +```python +from pydantic import BaseModel + +class User(BaseModel): + name: str + age: int + email: str +``` + +Now let's use it: + +```python +# This works perfectly +user = User(name="Jorge", age=28, email="jorge@example.com") +print(user.name) # Jorge + +# Pydantic even converts types when possible +user2 = User(name="Dave", age="30", email="dave@example.com") +print(user2.age) # 30 (converted from string to int!) + +# This will raise a validation error +try: + bad_user = User(name="Test", age="not a number", email="test@example.com") +except Exception as e: + print(e) + # age: Input should be a valid integer +``` + +That's it. You define a class, add type hints, and Pydantic handles the rest. No more writing manual `if isinstance(...)` checks everywhere. + +--- + +## Why Pydantic Matters + +I started paying attention to Pydantic when I noticed it's used _everywhere_ in the AI and data engineering ecosystem: + +- **FastAPI** uses Pydantic for request/response validation +- **LangChain** uses Pydantic for structured outputs from LLMs +- **PydanticAI** is a whole agent framework built on top of it +- **OpenAI's API** returns structured data that maps perfectly to Pydantic models + +### Benefits of using Pydantic: + +- **Catches bugs early** by validating data at runtime +- **Self-documenting code** — your models describe the data shape +- **Automatic type coercion** — converts compatible types for you +- **JSON serialization** — convert to/from JSON with one method call +- **IDE support** — full autocomplete and type checking + +--- + +## Going Deeper: Nested Models and Validators + +Pydantic really shines when your data gets more complex. Let's look at nested models: + +```python +from pydantic import BaseModel, EmailStr, field_validator +from typing import List, Optional + +class Address(BaseModel): + street: str + city: str + country: str = "Canada" # Default value + +class Employee(BaseModel): + name: str + age: int + email: str + address: Address # Nested model! + skills: List[str] = [] # List with default + + @field_validator("age") + @classmethod + def age_must_be_positive(cls, v): + if v < 0: + raise ValueError("Age must be positive") + return v +``` + +Now we can create an employee with a nested address: + +```python +emp = Employee( + name="Jorge", + age=28, + email="jorge@example.com", + address={"street": "123 Main St", "city": "Calgary", "country": "Canada"}, + skills=["Python", "SQL", "Data Modeling"] +) + +print(emp.address.city) # Calgary +print(emp.model_dump()) # Convert to dictionary +print(emp.model_dump_json()) # Convert to JSON string +``` + +✅ Why this matters: In real-world applications, data is rarely flat. Pydantic handles nested structures, lists, optional fields, and custom validation, all with clean syntax. + +--- + +## Pydantic + AI: Structured LLM Outputs + +Here's where it gets really interesting for AI applications. LLMs return text, but often you need _structured_ data. Pydantic solves this: + +```python +from pydantic import BaseModel +from openai import OpenAI + +class MovieReview(BaseModel): + title: str + rating: float + summary: str + recommend: bool + +client = OpenAI() + +completion = client.beta.chat.completions.parse( + model="gpt-4o", + messages=[ + {"role": "system", "content": "Extract movie review details."}, + {"role": "user", "content": "I just watched Inception. Solid 9/10. Mind-bending plot with great visuals. Definitely watch it."} + ], + response_format=MovieReview, +) + +review = completion.choices[0].message.parsed +print(review.title) # Inception +print(review.rating) # 9.0 +print(review.recommend) # True +``` + +✅ Why this matters: Instead of parsing messy text or hoping the LLM returns valid JSON, Pydantic guarantees the output matches your schema. This is the foundation of reliable AI applications. + +--- + +## Quick Reference: Key Pydantic Features + +| Feature | What It Does | +|---|---| +| `BaseModel` | Define data models with type hints | +| `field_validator` | Add custom validation logic | +| `model_dump()` | Convert model to dictionary | +| `model_dump_json()` | Convert model to JSON string | +| `model_validate()` | Create model from dictionary | +| `Optional[type]` | Mark fields as optional | +| Default values | Set fallback values for fields | + +--- + +## Summary + +Pydantic takes Python's type hints and turns them into a runtime validation system. You define what your data should look like, and Pydantic enforces it. No more silent bugs from bad data, no more manual type checking, no more guessing what shape your data is in. + +If you're building anything with Python, whether it's an API, a data pipeline, or an AI application, Pydantic should be in your toolkit. + +--- + +## Resources + +- 📺 [Pydantic Crash Course by Dave Ebbelaar](https://youtu.be/PkQIREapb9o) — The video that inspired this post +- 📖 [Pydantic Official Docs](https://docs.pydantic.dev/latest/) +- 🤖 [PydanticAI Framework](https://ai.pydantic.dev/) — For building AI agents with Pydantic + +--- + +## Final Thoughts + +When I was learning about RAG and LangChain, I kept seeing Pydantic show up in every tutorial. At first I thought it was just a nice-to-have, but once I started using it, I realized it's essential. It's the difference between code that _might_ work and code that _definitely_ works. + +Start simple. Define a `BaseModel`. Add some type hints. Let Pydantic do the heavy lifting. + +Cheers, + +Jorge Rocha diff --git a/_posts/2026-4-3-Disposable-Pixels.md b/_posts/2026-4-3-Disposable-Pixels.md new file mode 100644 index 000000000000..451e69216c6b --- /dev/null +++ b/_posts/2026-4-3-Disposable-Pixels.md @@ -0,0 +1,245 @@ +--- +layout: post +title: Disposable Pixels: The Future of AI-Generated Data Visualization +--- + +## A Different Kind of Chart + +Remember the first time you realized you could pass structured data to an image model and get back something that actually looked like a chart? No Matplotlib. No Plotly. No Streamlit server running somewhere. Just a prompt and an image. + +That moment surprised me. Not because the chart was perfect, but because it was recognizable. It had the right shape, the right labels, the right proportions. It felt like something shifted. + +This isn't a tutorial. I'm not here to teach you how to do this. What I want to do is think out loud about something that feels like it's about to change how analysts and BI developers work. + +--- + +## Quick Context: What Is Nano Banana Pro? + +Google launched Nano Banana Pro, an image generation model built on Gemini 3 Pro. You might think the big deal is artistic generation capability, but that's not what's interesting for data work. + +What matters is two specific properties that earlier image models didn't have reliably: it renders legible, accurate text inside images, and it understands structured data well enough to turn described numbers into something that looks like a real visualization. That combination opens the door. + +A model that can't render "Q3: $4.2M" accurately in a chart is useless for this work. Nano Banana Pro largely can. And now Nano Banana 2, the Flash-speed successor, just launched from Google. Things are getting better, and they're getting faster. + +--- + +## The Core Idea: The Model as a Rendering Engine + +Here's the conceptual shift, and it matters. In a Text-to-SQL workflow, once you have query results, you need something to render them. Today that something is always a chart library or a BI tool. Plotly, Power BI, Tableau, Looker. Always. + +But what if it didn't have to be? What if you just described the chart, the data, the style, and the model drew it? + +I'm not talking about replacing Power BI. That's not the conversation. I'm asking a different question: for conversational, ad hoc, one-shot visualization, the kind where someone asks "what did sales look like last quarter?" and you want an answer in seconds, does it matter whether the chart is rendered by Plotly or generated as an image? + +Let me introduce a term I want to use throughout: artifact visualization. Or "disposable pixels." These are visuals that exist for a single answer to a single question. They're not live. They're not interactive. They're not governed by row-level security or refresh schedules. They're meant to be read once and replaced next time someone asks something different. + +--- + +## Let's See It: Real Examples + +Here's where abstract becomes concrete. I'm showing you four real examples. For each one, you'll see the prompt I used (JSON-structured because that's how a real pipeline would pass data to the model), the image it generated, and my honest take on whether it worked. + +### Example 1: Bar Chart Over Time + +This is the clearest demonstration of the core idea. + +```json +{ + "chart_type": "bar", + "title": "Monthly Revenue – Q1 2025", + "x_axis": "Month", + "y_axis": "Revenue (CAD $M)", + "data": [ + { "month": "January", "value": 3.2 }, + { "month": "February", "value": 4.1 }, + { "month": "March", "value": 5.7 } + ], + "style": "professional, white background, teal bars, clean sans-serif font" +} +``` + +![Monthly Revenue Bar Chart](/images/disposable-pixels-example1.jpg) + +What it got right: proportions, labels, readability. The bars scale correctly relative to each other. The font is clean. The teal colors are exactly what I asked for. What it got wrong: minimal. This is a solid example. + +### Example 2: KPI Summary Card + +Now let's push into something less traditional. A single "slide" style image: one big headline number, a supporting metric, a small sparkline or trend arrow. The kind of thing you'd drop into a Teams message or a management email. + +```json +{ + "chart_type": "kpi_card", + "title": "Total Revenue – March 2025", + "layout": "single_card", + "style": "clean, professional, white background, white text, teal accents", + "primary_metric": { + "label": "Monthly Revenue", + "value": "$5.7M", + "font_size": "large", + "position": "center" + }, + "supporting_metric": { + "label": "vs. February 2025", + "value": "+39%", + "direction": "up", + "color": "green" + }, + "sparkline": { + "label": "Jan–Mar 2025 Trend", + "data": [3.2, 4.1, 5.7], + "color": "teal", + "position": "bottom" + } +} +``` + +![KPI Card - Total Revenue March 2025](/images/disposable-pixels-example2.jpg) + +This is where you start to see versatility. The model isn't just drawing bars anymore. It's composing a layout. Numbers, color, hierarchy, whitespace. It works. + +### Example 3: Multi-Metric Infographic Panel + +Push further. Two or three metrics, a chart, a headline insight. This is closer to a mini executive summary. This is where you see both the potential and the current ceiling. + +```json +{ + "chart_type": "infographic_panel", + "title": "Q1 2025 Sales Executive Summary", + "layout": "three_column_with_chart", + "style": "professional, white background, dark text, teal and slate blue accents, clean sans-serif font", + "headline_insight": "Q1 closed 22% above target, driven by the West region's strongest quarter on record.", + "kpi_cards": [ + { + "label": "Total Revenue", + "value": "$13.0M", + "change": "+22% vs. target", + "direction": "up", + "color": "green" + }, + { + "label": "Top Region", + "value": "West", + "change": "$5.7M — 44% of total", + "direction": "neutral", + "color": "teal" + }, + { + "label": "New Customers", + "value": "214", + "change": "+18% vs. Q4 2024", + "direction": "up", + "color": "green" + } + ], + "chart": { + "chart_type": "bar", + "title": "Revenue by Region", + "x_axis": "Region", + "y_axis": "Revenue (CAD $M)", + "data": [ + { "region": "West", "value": 5.7 }, + { "region": "East", "value": 3.8 }, + { "region": "Central", "value": 2.1 }, + { "region": "North", "value": 1.4 } + ] + } +} +``` + +![Q1 2025 Sales Executive Summary Panel](/images/disposable-pixels-example3.jpg) + +This one is impressive. You've got text, numbers, metrics, a chart, color coding, all composed into a coherent visual. The model handles it well. Not perfect, but compelling. + +### Example 4: The One That Didn't Work Great + +Here's the honest part. Not everything lands perfectly. + +```json +{ + "chart_type": "multi_line_chart", + "title": "Weekly Revenue by Product Category – Q1 2025", + "x_axis": "Week", + "y_axis": "Revenue (CAD $M)", + "style": "professional, white background, distinct color per line, include data point labels on every point", + "data": [ + { "week": "W1", "Hardware": 1.2, "Software": 0.9, "Services": 0.6, "Consulting": 0.4, "Support": 0.3 }, + { "week": "W2", "Hardware": 1.4, "Software": 1.1, "Services": 0.7, "Consulting": 0.5, "Support": 0.3 }, + { "week": "W3", "Hardware": 1.1, "Software": 1.3, "Services": 0.9, "Consulting": 0.6, "Support": 0.4 }, + { "week": "W4", "Hardware": 1.6, "Software": 1.2, "Services": 0.8, "Consulting": 0.4, "Support": 0.5 }, + { "week": "W5", "Hardware": 1.3, "Software": 1.5, "Services": 1.1, "Consulting": 0.7, "Support": 0.4 }, + { "week": "W6", "Hardware": 1.8, "Software": 1.4, "Services": 1.0, "Consulting": 0.8, "Support": 0.6 }, + { "week": "W7", "Hardware": 1.5, "Software": 1.6, "Services": 1.2, "Consulting": 0.9, "Support": 0.5 }, + { "week": "W8", "Hardware": 2.1, "Software": 1.7, "Services": 1.1, "Consulting": 0.7, "Support": 0.7 }, + { "week": "W9", "Hardware": 1.9, "Software": 1.9, "Services": 1.4, "Consulting": 1.0, "Support": 0.6 }, + { "week": "W10", "Hardware": 2.3, "Software": 2.0, "Services": 1.3, "Consulting": 1.1, "Support": 0.8 }, + { "week": "W11", "Hardware": 2.0, "Software": 2.2, "Services": 1.6, "Consulting": 0.9, "Support": 0.7 }, + { "week": "W12", "Hardware": 2.5, "Software": 2.1, "Services": 1.5, "Consulting": 1.2, "Support": 0.9 } + ], + "annotations": [ + { "week": "W4", "label": "Campaign Launch", "category": "Hardware" }, + { "week": "W9", "label": "Contract Renewal Window", "category": "Consulting" } + ] +} +``` + +![Weekly Revenue by Product Category - Complex Multi-Line Chart](/images/disposable-pixels-example4.jpg) + +The axis labels got crowded. Some of the data point labels overlap. The colors are distinct, but the chart feels busy. This is the moment where you realize the model is still learning. It can handle complexity, but it doesn't always choose simplicity when simple would be better. + +This is your "always verify" moment. You don't take a data-driven output at face value just because it looks professional. The model did the work, but you still need to check it. + +--- + +## Where This Fits (and Where It Doesn't) + +Let me be clear about the boundaries. + +This is not a Power BI replacement. It's not even trying to be. The moment you need interactivity, drill-through, row-level security, or a live refresh that updates every hour, you need a real BI layer. Nothing about this workflow changes that. + +Where it does fit: + +Conversational analytics. Inside a chat interface, someone asks "show me X" and you return a visual answer, not just a table. + +AI assistants that need to return something visual, not just text. A chart is more persuasive than a table. + +Ad hoc stakeholder snapshots that live in a Slack message or email. Disposable. Quick. Done. + +Rapid prototyping. What would this dashboard look like? Generate a few variations and see which resonates. + +Think of it as the difference between a printed snapshot and a live dashboard. Both have a place. One is for communication. The other is for exploration. + +--- + +## What Future Models Will Unlock + +This is the interesting part. Right now Nano Banana Pro is impressive but imperfect. Complex data still causes problems. A model might misinterpret information or produce factually incorrect results. Google DeepMind has been transparent about this limitation. It's real. + +But look at the trajectory. The jump from the original Nano Banana to Pro was significant on text rendering and data accuracy. Nano Banana 2 brought that Pro-level quality at Flash speed. The direction is clear. + +What becomes possible when the model is reliably accurate on data? + +Dynamic chart generation inside chat interfaces. Ask a question, get a visual answer, no frontend required. Just a conversation. + +Slide deck generation from a query result. Pass a structured JSON payload, get a properly formatted presentation slide back. Every slide uses the same brand colors, the same font, the same hierarchy. + +Embedded visualization in AI agents. Your analytics agent doesn't just return text. It returns a rendered artifact. Visual, immediate, no rendering server needed. + +Localized reporting. Nano Banana Pro already supports text rendering in multiple languages, meaning you could generate a chart with French labels for Quebec stakeholders and English labels for everyone else, from the same data, in the same pipeline. + +The piece that still needs to mature is factual reliability on numbers. When that's solved, and based on the current pace it will be, the use case becomes much stronger. + +--- + +## The Chart That Draws Itself + +Come back to the opening moment. The reason this feels different isn't the technology. It's the shift in mental model. + +For fifteen years, "visualizing data" meant choosing a tool and building something. You picked Tableau or Power BI or Plotly. You spent time configuring it. You deployed it. + +What's emerging is a world where you describe what you want to see and the model renders it. That's a small conceptual shift with large practical implications for anyone building on top of data. + +For people building Text-to-SQL pipelines, this is a natural next layer. The query generates the data. The model generates the visual. The human just asked a question in plain English. + +We're not fully there yet. The model still needs to get better at accuracy. The legal and compliance stories still need to mature. The workflow still needs some friction removed. + +But we're close enough that it's worth paying attention now. Not next year. Now. diff --git a/_sass/_dark-code.scss b/_sass/_dark-code.scss new file mode 100644 index 000000000000..2f25422a71d6 --- /dev/null +++ b/_sass/_dark-code.scss @@ -0,0 +1,42 @@ +// Dark theme for code blocks +.highlight { + background-color: #2d3748 !important; + border-radius: 6px; + padding: 16px; + margin: 16px 0; + + pre { + background-color: transparent !important; + margin: 0; + padding: 0; + } + + code { + background-color: transparent !important; + color: #e2e8f0 !important; + font-family: 'Monaco', 'Consolas', 'Courier New', monospace; + } +} + +// Inline code styling +code { + background-color: #4a5568 !important; + color: #e2e8f0 !important; + padding: 2px 6px; + border-radius: 3px; + font-family: 'Monaco', 'Consolas', 'Courier New', monospace; +} + +// Override any existing code block styles +pre { + background-color: #2d3748 !important; + color: #e2e8f0 !important; + border-radius: 6px; + padding: 16px; + overflow-x: auto; + + code { + background-color: transparent !important; + padding: 0; + } +} diff --git a/about.md b/about.md index bc21f5731bf4..e5a309e0c0ff 100644 --- a/about.md +++ b/about.md @@ -4,12 +4,13 @@ title: About permalink: /about/ --- -Some information about you! +
+ Jorge Rocha +
-### More Information +My name is Jorge Rocha. I'm a Business Intelligence Developer with nearly a decade of experience across a range of industries including retail, oil and gas, power, food manufacturing, and consulting. I specialize in designing efficient, scalable data models that help businesses turn raw data into actionable insights. -A place to include any other types of information that you'd like to include about yourself. +I have a deep passion for building creative solutions using both code and hardware. Whether it’s automating workflows, visualizing complex systems, prototyping with microcontrollers, or tinkering with DIY electronics projects, I enjoy solving problems at the intersection of data and technology. -### Contact me +Outside of work, I love spending time with my wife and two boys, especially outdoors. Whether we’re hiking, biking, or just exploring new parks, being in nature with my family is one of my favorite ways to recharge. -[email@domain.com](mailto:email@domain.com) \ No newline at end of file diff --git a/images/RAG_Basic_Standard.png b/images/RAG_Basic_Standard.png new file mode 100644 index 000000000000..43c1a8c0320d Binary files /dev/null and b/images/RAG_Basic_Standard.png differ diff --git a/images/cubometrics.png b/images/cubometrics.png new file mode 100644 index 000000000000..f7b2bec39a3b Binary files /dev/null and b/images/cubometrics.png differ diff --git a/images/diagram_breakdown.png b/images/diagram_breakdown.png new file mode 100644 index 000000000000..70bc35549c50 Binary files /dev/null and b/images/diagram_breakdown.png differ diff --git a/images/disposable-pixels-example1.jpg b/images/disposable-pixels-example1.jpg new file mode 100644 index 000000000000..8797651f46a6 Binary files /dev/null and b/images/disposable-pixels-example1.jpg differ diff --git a/images/disposable-pixels-example2.jpg b/images/disposable-pixels-example2.jpg new file mode 100644 index 000000000000..a5d6c9525f52 Binary files /dev/null and b/images/disposable-pixels-example2.jpg differ diff --git a/images/disposable-pixels-example3.jpg b/images/disposable-pixels-example3.jpg new file mode 100644 index 000000000000..041e2ad4af1b Binary files /dev/null and b/images/disposable-pixels-example3.jpg differ diff --git a/images/disposable-pixels-example4.jpg b/images/disposable-pixels-example4.jpg new file mode 100644 index 000000000000..3e6c1715c009 Binary files /dev/null and b/images/disposable-pixels-example4.jpg differ diff --git a/images/how_wrenai_works.png b/images/how_wrenai_works.png new file mode 100644 index 000000000000..172aad54148e Binary files /dev/null and b/images/how_wrenai_works.png differ diff --git a/images/jorge-rocha.png b/images/jorge-rocha.png new file mode 100644 index 000000000000..3da10ffe096c Binary files /dev/null and b/images/jorge-rocha.png differ diff --git a/style.scss b/style.scss index 3915a9024469..9277b7fcfd82 100644 --- a/style.scss +++ b/style.scss @@ -7,6 +7,7 @@ @import "reset"; @import "variables"; +@import "dark-code"; // Syntax highlighting @import is at the bottom of this file /**************/ @@ -283,6 +284,7 @@ footer { text-align: center; } + // Settled on moving the import of syntax highlighting to the bottom of the CSS // ... Otherwise it really bloats up the top of the CSS file and makes it difficult to find the start @import "highlights";