How to actually integrate AI into your existing workflows, without breaking everything
TL;DR
Difficulty: Intermediate
AI projects rarely stall on the model. They stall on the data.
- Order of work: define the problem without the word AI, audit the data, clean it, choose the integration pattern, then start with one measured use case.
- Three patterns add AI without replacing systems: a service wrapper with a fallback, an event listener on Spree::Bus, and middleware between systems.
- MCP puts one standard server in front of each legacy system instead of a web of custom integrations.
- Data protection: send identifiers, not identities, use business terms with a processing agreement, and log what you sent.
- Start with cart recovery, product data enrichment, forecasting, ticket classification or campaign optimisation, one at a time.
Most AI projects stall, and almost never because of the model. I walk through the order of work that holds up, three ways to add AI to an existing Rails and Solidus stack without replacing it, what MCP changes for legacy systems, the data protection basics, and five realistic first projects, worked through on an invented Limerick car marketplace.
Why most AI projects stall, and it is rarely the AI
Let me save you six months and a lot of frustration. Most AI projects that stall do not stall because the model was not good enough. They stall because the data was rubbish.
The research points the same way. Gartner predicted in February 2025 that through 2026 organisations will abandon 60% of AI projects that are not supported by AI ready data.1 A survey of more than 550 data and analytics professionals by Precisely and Drexel University's LeBow College of Business found that 77% rated their organisation's data quality as average at best, up from 66% the year before, an eleven point fall.2 And BARC's Data, BI and Analytics Trend Monitor 2026, with 1,579 participants, put data quality management back in first place, level with data security and privacy, ahead of the newer AI topics.3
The pattern is always the same. A company gets excited about AI. It buys or builds a model and feeds it its data. The data is inconsistent, duplicated, out of date or incomplete. The model produces unreliable results, the project stalls, and six months and a budget later everyone concludes that "AI doesn't work for us". The AI was not the problem. Fixing the data should have been the first step.
I know this because I've lived it. When I started building GrowCentric.ai, my marketing optimisation SaaS, the machine learning models were not the hard part. The hard part was getting clean, consistent data out of the messy reality of real eCommerce businesses. And when I built AI assisted search and recommendations for a car marketplace running on Rails and Solidus, the challenge was not the algorithms. It was the same model entered in several different ways, mileage and engine power in mixed units, colour names all over the place, duplicate customer records and old supplier feeds.
Worked example: one car, five spellings
Adare Motor Exchange is an invented used car marketplace in Limerick at adaremotors.example, with 1,800 listings entered by dozens of dealers, a good few of them selling imports from the UK, so miles and kilometres sit side by side. The same car turns up as:
- "BMW 320i", 45,000 miles, 184 bhp, "black"
- "BMW 3 Series 320i", "45000", 135 kW, "Black"
- "3 Series 320 i", 72,420 km, 184PS, "jet black"
- "bmw 320I", "45k", no power given, "Black Sapphire metallic"
- "BMW 320i M Sport", 45,000 miles, 184 bhp, "BLACK"
A recommendation model sees five different cars. A search for "black BMW 3 Series under 50,000 miles" finds one of them, maybe two. No model in the world makes sense of that without cleaning it first. So that is where to start: not with AI, with data.
The right order of operations
Here is the order that works, from projects I've built and projects I've rescued.
Read it from the top. The only loop is the data check: if the audit says the data cannot support the problem, the next job is cleaning, not modelling.
Step 1: define the business problem. Not "we want to use AI" but "we want to recover more abandoned carts", "we want to know which cars will sell within seven days" or "we want support tickets routed to the right person". If you cannot state the problem without mentioning AI, you are not ready.
Step 2: audit your data. Before a line of AI code, look at the data. How complete, how consistent, how accurate, how current? Where are the duplicates and the gaps? The audit tells you whether you are weeks or months away. For a Rails and Solidus shop it can be a short class:
class DataQualityAudit
def run
{ products: audit_products, customers: audit_customers, inventory: audit_inventory }
end
private
def audit_products
{
total: Spree::Product.count,
missing_descriptions: Spree::Product.where(description: [nil, ""]).count,
without_images: Spree::Product.where.missing(:variant_images).count,
variants_without_price: Spree::Variant.where.missing(:prices).count,
duplicate_skus: Spree::Variant.where.not(sku: [nil, ""]).group(:sku).having("count(*) > 1").count.size
}
end
def audit_customers
users = Spree.user_class
{
total: users.count,
duplicate_emails: users.group("LOWER(email)").having("count(*) > 1").count.size,
without_address: users.where.missing(:addresses).count,
untouched_two_years: users.where(updated_at: ...2.years.ago).count
}
end
def audit_inventory
{
negative_stock: Spree::StockItem.where("count_on_hand < 0").count,
listed_but_unsellable: Spree::StockItem.where(count_on_hand: 0, backorderable: false)
.joins(variant: :product).merge(Spree::Product.available).count
}
end
end
Spree.user_class is the configured user model, and Solidus products and variants are soft deleted, so the default scopes already leave deleted records out.
A single number per area helps you track progress. A simple completeness score is the share of required fields that are filled:
Here is the completeness score from 0 to 1, is the number of required fields that are actually filled, is the number of required fields per record and is the number of records. It is an ordinary rate, events over opportunities. Foundations: ratios and rates
Worked example: Adare's listing completeness
Adare requires five fields per listing: make and model, mileage, power, colour and at least one photo. With 1,800 listings there are required fields. The audit finds 540 listings without power, 210 without a photo and 90 without mileage, so fields are empty and are filled.
So is 90.7%. That sounds grand until you notice that the empty fields are exactly the ones a buyer filters on. The score tells you how much is missing. Which fields are missing tells you whether it matters.
Step 3: clean and normalise. The unglamorous work that makes everything else possible. Deduplicate, standardise formats, fill gaps where you can, and add validation so new dirty data cannot get in. For Adare that means a normaliser for listings:
class ListingNormaliser
MAKE_ALIASES = { "bmw" => "BMW", "vw" => "Volkswagen", "merc" => "Mercedes-Benz", "mercedes" => "Mercedes-Benz" }.freeze
KM_PER_MILE = 1.609344
KW_PER_BHP = 0.7457
KW_PER_PS = 0.7355
def normalise(listing)
{
make: MAKE_ALIASES.fetch(listing[:make].to_s.strip.downcase, listing[:make].to_s.strip),
mileage_miles: mileage_in_miles(listing[:mileage]),
power_kw: power_in_kw(listing[:power]),
colour: listing[:colour].to_s.strip.downcase.presence
}
end
private
def mileage_in_miles(value)
text = value.to_s.downcase.strip
return nil if text.empty?
number = text.delete(",").to_f
number *= 1_000 if text.match?(/\d\s*k\b/)
number /= KM_PER_MILE if text.include?("km")
number.positive? ? number.round : nil
end
def power_in_kw(value)
text = value.to_s.downcase
number = text[/\d+(\.\d+)?/].to_f
return nil unless number.positive?
if text.include?("kw") then number.round
elsif text.include?("ps") then (number * KW_PER_PS).round
else (number * KW_PER_BHP).round # bhp or hp
end
end
end
Note the two conversion factors. Brake horsepower and metric horsepower (PS) are not the same unit, and a normaliser that uses one factor for both is quietly wrong by about 1.4%.
Worked example: the five BMWs, normalised
Listing 3 says 72,420 km and 184PS. Mileage: miles. Power: kW. Listing 1 says 184 bhp: kW. Listing 2 already says 135 kW. Listing 4's "45k" becomes 45,000 miles.
After the normaliser, four of the five agree on mileage, and the power figures sit at 135 to 137 kW, which is close enough to flag as probably the same car for a person to check. The colour still needs a lookup table (black, jet black, Black Sapphire metallic all map to "black" for search), which is a morning's work. Only then is the data worth showing to a model.
Step 4: choose the integration pattern. Now, and only now, think about how AI connects to your systems.
Step 5: start small, measure everything, iterate.
Three patterns for adding AI without replacing anything
Most businesses cannot rip out their systems and start again. You have a Rails application that has run for years, a Solidus shop with thousands of products and customers, staff who depend on the workflows, and integrations with suppliers, payment providers and couriers. You do not need to replace any of it. You layer AI on top.
Pattern 1: the service wrapper. Put the AI behind a service your application calls. The application does not need to know that machine learning is involved. It asks, gets an answer and uses it, and if the AI is slow or down, it falls back.
class ProductRecommendationService
def recommendations_for(user, limit: 6)
fetch_ai_recommendations(user, limit).presence || fallback_recommendations(user, limit)
end
private
def fetch_ai_recommendations(user, limit)
response = AiClient.post("/recommendations", user_id: user.id, limit: limit)
in_stock(Spree::Product.where(id: response.fetch("product_ids", []))).limit(limit)
rescue AiClient::Error, Timeout::Error => e
Rails.logger.warn("Recommendations unavailable: #{e.message}")
nil
end
def fallback_recommendations(user, limit)
in_stock(Spree::Product.available).order(available_on: :desc).limit(limit)
end
def in_stock(products)
products.joins(:variants_including_master).merge(Spree::Variant.in_stock).distinct
end
end
If the AI service fails, the shop shows the newest products in stock instead. Customers never see an error. AI should improve a workflow, never become its single point of failure.
Notice what is sent: a user id and a number. Not the customer's name, email or address. More on that below.
Pattern 2: the event listener. Your shop already announces what happens. Solidus publishes events such as order_finalized on its own bus, Spree::Bus, built on the Omnes library, and you can register your own.4 An AI layer subscribes to those events and acts, without touching the checkout:
# config/initializers/omnes.rb
Rails.application.config.to_prepare do
Spree::Bus.register(:cart_abandoned) unless Spree::Bus.registry.registered?(:cart_abandoned)
Adare::AiSubscriber.new.subscribe_to(Spree::Bus)
end
# app/subscribers/adare/ai_subscriber.rb
module Adare
class AiSubscriber
include Omnes::Subscriber
handle :order_finalized, with: :after_order
handle :cart_abandoned, with: :after_abandonment
def after_order(event)
order = event.payload[:order]
CrossSellAnalysisJob.perform_later(order.id)
DemandForecastUpdateJob.perform_later(order.id)
end
def after_abandonment(event)
CartRecoveryJob.perform_later(event.payload[:order].id)
end
end
end
# A recurring job publishes the custom event for carts left alone for an hour
Spree::Bus.publish(:cart_abandoned, order: order)
The subscriber only enqueues jobs, so the checkout never waits for a model to think. Your Solidus checkout, payments and order management do not change. The AI layer listens and adds to them.
Pattern 3: the middleware layer. When AI needs to sit between two systems and improve the data passing through, a middleware layer does the job. I use this pattern in GrowCentric.ai, which sits between eCommerce shops and advertising platforms such as Google Ads and Meta. It reads performance data, enriches it with its own analysis and passes adjusted campaign settings back. The shop does not change at all. GrowCentric only needs API access to read performance and write campaign changes.
Worked example: choosing a pattern at Adare
Three jobs at Adare. "Show similar cars on a listing page" is a question the page asks and needs answered in under a second, so it is a service wrapper with a fallback. "When a car sells, update the demand forecast for that model" reacts to something that happened, so it is an event listener. "Adjust Adare's Google Ads bids by how fast each model sells" sits between two systems, so it is middleware. The pattern follows from the question: asked, happened, or in between.
Legacy systems, and what MCP changes
Legacy systems are where most businesses get stuck. A supplier sends product data as a CSV attachment. The accounting system only speaks SOAP XML. The warehouse system from 2015 has a barely documented REST API. Customer data is split across three systems that were never joined up. That is the reality for every mid market eCommerce business I've worked with.
The traditional answer is custom integrations between each pair of systems. With systems that can mean up to integrations, so ten systems means up to , and each change breaks several of them.
The Model Context Protocol (MCP), which I covered in AI agents vs chatbots, changes the shape. Anthropic published it in November 2024 as an open standard for connecting AI assistants to the systems where data lives,5 and in December 2025 donated it to the Agentic AI Foundation under the Linux Foundation, so no single vendor controls it.6 Instead of point to point integrations, you put one MCP server in front of each system. Each server describes what its system can do in a standard way, and any MCP aware agent can use it. Ten systems, ten servers.
The official Ruby SDK, the mcp gem, defines each capability as a tool class:7
class CheckSupplierStock < MCP::Tool
description "Check stock at the supplier for one SKU"
input_schema(properties: { sku: { type: "string" } }, required: ["sku"])
class << self
def call(sku:, server_context:)
stock = LegacySupplierSoapClient.check_stock(sku) # wraps the old SOAP API
MCP::Tool::Response.new([{ type: "text", text: { sku: sku, available: stock }.to_json }])
end
end
end
class LatestSupplierCatalogue < MCP::Tool
description "The supplier's latest catalogue, parsed and normalised"
input_schema(properties: { since: { type: "string", format: "date" } })
class << self
def call(server_context:, since: nil)
rows = SupplierCsvImporter.new.fetch_and_normalise(since: since) # reads the CSV attachments
MCP::Tool::Response.new([{ type: "text", text: rows.to_json }])
end
end
end
server = MCP::Server.new(name: "adare_supplier", tools: [CheckSupplierStock, LatestSupplierCatalogue])
The agent never needs to know that one supplier sends CSV attachments while another has a SOAP API. The server hides that. Notice what is missing: a tool that places purchase orders. Reading comes first. Anything that commits money waits until the read only tools have earned trust, and then goes through a person.
Data protection before the first API call
Every request you send to a cloud AI service leaves your systems. If it contains anything that identifies a person, it is processing of personal data under the GDPR, which applies in Ireland directly as an EU regulation, and the usual rules apply: the data must be limited to what is necessary for the purpose,8 and a provider that processes it on your behalf needs a contract with the terms Article 28 sets out.9 The Irish regulator is the Data Protection Commission (DPC), established under the Data Protection Act 2018,10 and the DPC's guidance on AI, large language models and data protection is the place to start.11
The risk is not theoretical. In May 2023 Samsung restricted staff use of generative AI tools such as ChatGPT on company computers after cases of misuse, reportedly including confidential code entered into ChatGPT.12 Nobody meant harm. Someone wanted help with a bug. My article on AI in business goes through more incidents like it and how to route sensitive work so it never leaves the building, and building GDPR compliant AI features covers the product side.
In integration code, three habits cover most of it. Send identifiers, not identities: a user id, never a name and an email, unless the task truly needs them. Use business or API terms with a processing agreement, never a personal account. And log what you sent, so you can answer the question "what did we give the AI?" when someone asks.
Worked example: what Adare sends to the recommendation service
The first draft of Adare's integration sent the full user record: name, email, phone number, Eircode and the cars viewed. The question to ask of each field is whether the recommendation needs it.
- Name: no.
- Email and phone: no.
- Eircode: only the routing key, if distance matters (V94, not the full code).
- Cars viewed: yes, as listing ids.
- A stable user id, so the service can learn: yes, a random identifier, not the database id.
Five personal fields become one random id, one routing area and a list of listing ids. The recommendations are just as good, and a leak at the provider would expose almost nothing.
Five realistic starting points
Enough theory. Here are five starting points, ranked by impact and difficulty. Pick one. Get it working. Then move to the next.
1. Smarter cart recovery (low difficulty, high impact). Abandoned carts are one of the biggest revenue leaks in eCommerce. Baymard Institute's average across 50 studies puts the abandonment rate at about 70%.13 The AI version goes beyond "you left something in your basket". It estimates why the customer left (delivery cost shock, comparison shopping, just browsing), picks the channel and the incentive, and times the follow up. On the car marketplace I mentioned, this was the most useful AI integration I built.
class CartRecoveryAgent
def recover(order)
user = order.user
return unless user&.marketing_consent? # the shop's own consent record: no consent, no email or advert
reason = AbandonmentClassifier.predict(signals_for(order))
strategy = RecoveryStrategy.for(reason)
CartRecoveryMailer.recovery(order, template: strategy.template, incentive: strategy.incentive)
.deliver_later(wait: strategy.delay)
AgentAuditLog.create!(agent_name: "CartRecovery", action_type: "recovery_email",
decision_payload: { order_id: order.id, reason: reason, strategy: strategy.to_h })
end
private
def signals_for(order)
{
reached_delivery_step: order.state.in?(%w[delivery payment confirm]),
first_time_buyer: order.user.orders.complete.none?,
items: order.line_items.count,
basket_value: order.item_total.to_f
}
end
end
Two details matter. The consent check comes first: a recovery email is marketing, and so is a retargeting advert. And the mailer is called through its own method, recovery, never through Ruby's send, which would call a method on the class rather than send an email.
Worked example: is it worth building?
The example's assumptions: Adare's parts shop sees 600 abandoned carts a month at an average of €85, 60% of those customers have given marketing consent, and the smarter emails recover 6% of the carts they go to, against 3% for the old fixed reminder.
Carts that can be emailed: . Old reminder: orders. New agent: orders. The difference is a month in revenue. If the build costs less than the margin on that over a year, it is worth it. The 6% is the example's assumption, and the first month of measurement replaces it with a real number. Foundations: expected value
2. Product data enrichment (low difficulty, medium impact). Thin or inconsistent product descriptions can be improved with AI, but only after the structured data is clean. Generate or improve descriptions, extract attributes from free text, and keep categories consistent. Do not apply the output blindly:
class ProductEnrichmentService
def enrich(product)
return unless product.description.to_s.length < 100 || product.meta_description.blank?
properties = product.product_properties.joins(:property).pluck("spree_properties.name", :value).to_h
result = AiClient.post("/enrich-product", name: product.name, description: product.description.to_s,
properties: properties, taxons: product.taxons.pluck(:name))
if result["confidence"].to_f >= 0.8
product.update!(description: result["description"], meta_description: result["meta_description"])
else
EnrichmentReview.create!(product: product, proposal: result) # a person decides
end
end
end
Below the confidence threshold, a person reviews the proposal. Note that only product data goes to the service, never customer data.
3. Demand forecasting (medium difficulty, high impact). As I described in agentic AI for eCommerce, stock management is one of the most valuable uses of AI. It needs clean history first. If stock counts were wrong, sales data has gaps or categories are inconsistent, the forecast will be unreliable. Start with at least 12 months of clean orders, accurate stock and consistent categories, then build a basic forecasting model that improves over time.
4. Support ticket classification (low difficulty, medium impact). AI can tag incoming tickets by type (return, delivery question, product question, complaint), urgency and tone, and route them to the right person. That needs no new support system, just an event listener that reads each new ticket and adds labels.
5. Marketing campaign optimisation (medium difficulty, high impact). This is what GrowCentric.ai does. Instead of adjusting budgets, bids and targeting by hand, AI agents monitor performance continuously and make the routine optimisation decisions. The prerequisite is clean tracking. If attribution is broken or conversion tracking incomplete, the AI optimises for the wrong thing.
What I do for clients
To be concrete about how this maps to my work:
Data audits and clean up. I audit a Solidus shop's data (product completeness, duplicate customers, stock accuracy, order integrity), then build the normalisation and validation that keep new dirty data out. It is the foundation for everything else.
AI ready architecture on Rails. Events, background jobs and fallbacks, so the application is ready for AI and an AI failure never breaks the shop.
MCP servers. Servers that expose existing systems (Solidus, supplier feeds, warehouse, accounting) to AI agents in a standard way, so legacy systems join in without being replaced.
Agents themselves. Cart recovery, forecasting, ticket classification, order routing, each with tiered autonomy, audit logs and the guardrails from my agentic AI and agents vs chatbots articles.
Compliance built in. The GDPR from the first line of code, and, because an Irish business sits inside the EU, the Cyber Resilience Act and the NIS2 Directive. Deploying AI that breaks data protection law is not an advantage, it is a liability.
The honest reality check
Some uncomfortable truths to finish.
It takes longer than you think. Budget three to six months for a meaningful integration, not three to six weeks. The data clean up alone can take weeks, integration testing takes longer than development, and your team needs time to trust the AI's decisions before it gets more autonomy.
You need less AI than you think. Most businesses would gain more from clean data and better basic analytics than from a sophisticated model. If you cannot answer basic questions about customer behaviour, your conversion funnel and stock turnover with the data you have, AI will not help yet. Fix the basics first.
The first version will be underwhelming. Expect a 5% to 10% improvement from a first integration, not a transformation. That is normal. The value compounds as the models see more data and you extend them to more uses.
You cannot outsource understanding. Even if you hire someone (like me) to build it, your team needs to understand what it does and why. If nobody can explain how the cart recovery agent decides, nobody can improve it, debug it or trust it. Handing over the knowledge matters as much as the code.
The businesses that do well with AI are not the ones with the most impressive models. They are the ones with the cleanest data, the most careful integration, and the discipline to start small and measure. It is not glamorous. It works.
Footnotes
-
Freevacy, Gartner reveals 60% of AI projects with data issues will fail, 26 February 2025, reporting the Gartner press release Lack of AI-Ready Data Puts AI Projects at Risk of the same date, https://www.freevacy.com/news/gartner/gartner-reveals-60-of-ai-projects-with-data-issues-will-fail/6155
-
Rachel Galvez, 2025 planning insights: data quality remains the top data integrity challenge, Precisely, 5 November 2024, on the 2025 Outlook: Data Integrity Trends and Insights report with Drexel University LeBow College of Business, https://www.precisely.com/blog/data-integrity/2025-planning-insights-data-quality-remains-the-top-data-integrity-challenges
-
BARC, Data quality beats AI hype: BARC publishes the Data, BI and Analytics Trend Monitor 2026, 12 November 2025, https://barc.com/news/barc-publishes-the-data-bi-and-analytics-trend-monitor-2026/
-
Solidus Guides, Subscribing to events, accessed November 2026, https://guides.solidus.io/customization/subscribing-to-events
-
Anthropic, Introducing the Model Context Protocol, 25 November 2024, https://www.anthropic.com/news/model-context-protocol
-
MCP Core Maintainers, MCP joins the Agentic AI Foundation, Model Context Protocol blog, 9 December 2025, https://blog.modelcontextprotocol.io/posts/2025-12-09-mcp-joins-agentic-ai-foundation/
-
Model Context Protocol, Ruby SDK (the mcp gem), GitHub, accessed November 2026, https://github.com/modelcontextprotocol/ruby-sdk
-
Regulation (EU) 2016/679 (General Data Protection Regulation), Article 5(1)(c), data minimisation, EUR-Lex, https://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri=CELEX:32016R0679
-
Regulation (EU) 2016/679 (General Data Protection Regulation), Article 28, processor, EUR-Lex, https://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri=CELEX:32016R0679
-
Data Protection Act 2018 (Ireland), section 10, establishment of the Data Protection Commission, Irish Statute Book, https://www.irishstatutebook.ie/eli/2018/act/7/section/10/enacted/en/html
-
Data Protection Commission, AI, Large Language Models and Data Protection, 18 July 2024, https://www.dataprotection.ie/en/dpc-guidance/blogs/AI-LLMs-and-Data-Protection
-
Arjun Kharpal, Samsung bans use of generative AI tools like ChatGPT after April internal data leak, CNBC, 2 May 2023, https://www.cnbc.com/2023/05/02/samsung-bans-use-of-ai-like-chatgpt-for-staff-after-misuse-of-chatbot.html
-
Baymard Institute, Cart abandonment rate statistics, updated 28 October 2026, https://baymard.com/lists/cart-abandonment-rate