Case Study18 min readDecember 10, 2024

How We Process 6.8M PKR Monthly: Polaris ERP Architecture

A deep dive into the technical architecture of Polaris ERP, our AI-powered business management system. Learn about scaling, real-time processing, and AI integration.

CST

Commit Software Team

Product Engineering

Introduction


Polaris ERP started as a solution to a simple problem: Pakistani SMBs needed a modern, AI-powered business management system that wasn't SAP-expensive but wasn't local-software-limited either. Today, Polaris processes over 6.8 million PKR in monthly transactions across dozens of businesses, with AI features that understand both Urdu and English.

This article provides a technical deep-dive into the architecture that makes this possible.

System Overview


Polaris is built on a modern stack designed for reliability, real-time performance, and AI integration:

  • Frontend: Next.js 14 with React Server Components

  • Backend: Node.js with tRPC for type-safe APIs

  • Database: PostgreSQL with Prisma ORM

  • AI Layer: Google Gemini 2.0 Flash via Vertex AI

  • Real-time: WebSockets for live updates

  • Infrastructure: Google Cloud Platform (Cloud Run, Cloud SQL)

The Three Pillars


### Pillar 1: Real-Time Transaction Processing

In a retail environment, transactions must be processed instantly. A cashier can't wait 3 seconds for the system to respond.

Architecture Decision: Optimistic Updates with Reconciliation

// Client-side: Immediate UI update
async function processTransaction(transaction: Transaction) {
// 1. Optimistically update local state
updateLocalInventory(transaction);
updateLocalLedger(transaction);

// 2. Sync to server in background
const result = await trpc.transactions.create.mutate(transaction);

// 3. Reconcile if needed
if (result.conflicts) {
await reconcileConflicts(result.conflicts);
}
}

Result: P99 UI latency of 50ms for transaction processing, even on 3G connections.

### Pillar 2: Multi-Tenant Data Isolation

Each business's data must be completely isolated, but we also need to maintain a shared infrastructure for cost efficiency.

Architecture Decision: Row-Level Security (RLS) in PostgreSQL

-- Every table includes tenant_id
CREATE TABLE transactions (
id UUID PRIMARY KEY,
tenant_id UUID NOT NULL REFERENCES tenants(id),
amount DECIMAL(15,2) NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);

-- RLS policy ensures isolation
ALTER TABLE transactions ENABLE ROW LEVEL SECURITY;

CREATE POLICY tenant_isolation ON transactions
USING (tenant_id = current_setting('app.tenant_id')::UUID);

Result: Complete data isolation with zero application-level filtering bugs possible.

### Pillar 3: AI That Actually Helps

The AI features in Polaris aren't gimmicks. They solve real problems:

  • Natural Language Queries: Ask "Which product sold most in Lahore?" in Urdu or English

  • Smart Reconciliation: Automatically match payments to invoices

  • Demand Forecasting: Predict inventory needs using Prophet

Architecture Decision: Gemini 2.0 with Function Calling

const tools = [
{
name: "query_sales",
description: "Query sales data by various filters",
parameters: {
type: "object",
properties: {
product_id: { type: "string" },
date_range: { type: "object" },
location: { type: "string" }
}
}
},
{
name: "query_inventory",
description: "Get current inventory levels",
parameters: {
type: "object",
properties: {
product_id: { type: "string" },
warehouse_id: { type: "string" }
}
}
}
];

async function handleNaturalLanguageQuery(query: string, tenantId: string) {
const response = await gemini.generateContent({
model: "gemini-2.0-flash",
contents: [{ role: "user", parts: [{ text: query }] }],
tools: [{ functionDeclarations: tools }]
});

// Execute the function call
const functionCall = response.candidates[0].content.parts[0].functionCall;
const result = await executeDatabaseQuery(functionCall, tenantId);

// Generate natural language response
return await gemini.generateContent({
contents: [
{ role: "user", parts: [{ text: query }] },
{ role: "model", parts: [{ functionCall }] },
{ role: "function", parts: [{ functionResponse: result }] }
]
});
}


Scaling Challenges and Solutions


### Challenge 1: Peak Load During Business Hours

Pakistani businesses see traffic spikes from 10 AM to 2 PM and 5 PM to 9 PM. During Eid seasons, traffic can be 10x normal.

Solution: Auto-scaling with Cloud Run

# Cloud Run configuration
spec:
template:
metadata:
annotations:
autoscaling.knative.dev/minScale: "1"
autoscaling.knative.dev/maxScale: "100"
autoscaling.knative.dev/target: "80" # Target 80% CPU utilization
spec:
containerConcurrency: 80
timeoutSeconds: 300

Result: Automatic scaling from 1 to 50+ instances during Eid, back to 1 during off-hours. Cost savings of 60% compared to always-on VMs.

### Challenge 2: Database Connection Limits

With auto-scaling containers, database connections became the bottleneck. PostgreSQL has a hard connection limit.

Solution: PgBouncer Connection Pooling

[pgbouncer]
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 20
min_pool_size = 5
reserve_pool_size = 5

Result: Handling 1000+ concurrent users with only 20 database connections.

### Challenge 3: Report Generation During Peak Hours

Generating reports (which involve heavy queries) was slowing down transaction processing.

Solution: Read Replicas for Analytics

// Prisma client with read replica
const prisma = new PrismaClient({
datasources: {
db: {
url: isReportQuery ? process.env.READ_REPLICA_URL : process.env.DATABASE_URL
}
}
});

// Decorator for automatic routing
function useReadReplica() {
return function (target: any, key: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = async function (...args: any[]) {
return runWithReadReplica(() => original.apply(this, args));
};
};
}

@useReadReplica()
async generateSalesReport(tenantId: string, dateRange: DateRange) {
// This automatically uses the read replica
}

Result: Report generation time reduced by 40%, zero impact on transaction processing.

The Smart Reconciliation Engine


One of Polaris's most valuable features is Smart Ledger Reconciliation. Here's how it works:

### Problem

Businesses receive payments that don't exactly match invoices. A customer might pay 10,000 PKR against three invoices totaling 10,250 PKR. Manual reconciliation takes hours.

### Solution: AI-Powered Matching

interface Payment {
amount: number;
customer_id: string;
reference?: string;
date: Date;
}

interface Invoice {
id: string;
amount: number;
customer_id: string;
due_date: Date;
status: 'pending'

'partial'
'paid';
}

async function smartReconcile(payment: Payment): Promise<ReconciliationResult> {
// 1. Get pending invoices for customer
const pendingInvoices = await getInvoicesForCustomer(payment.customer_id);

// 2. Use AI to find best matches
const prompt =
A customer made a payment of ${payment.amount} PKR.
They have these pending invoices:
${pendingInvoices.map(i => "- Invoice #" + i.id + ": " + i.amount + " PKR due " + i.due_date).join('\n')}

Reference note: ${payment.reference || 'None'}

Determine which invoices this payment should be applied to.
Consider partial payments and payment tolerance of 2%.
Return a JSON array of {invoice_id, amount_to_apply}.
;

const aiSuggestion = await gemini.generateContent(prompt);

// 3. Apply with human review for low confidence matches
if (aiSuggestion.confidence < 0.9) {
return { status: 'review_required', suggestion: aiSuggestion };
}

return applyReconciliation(payment, aiSuggestion.matches);
}

Result: 92% of reconciliations are now automatic, reducing daily accounting work by 2 hours.

Security Architecture


### Authentication: Session-Based with Refresh Tokens

// JWT access token (15 min expiry)
const accessToken = jwt.sign(
{ userId, tenantId, permissions },
ACCESS_TOKEN_SECRET,
{ expiresIn: '15m' }
);

// Refresh token (7 days, stored in httpOnly cookie)
const refreshToken = jwt.sign(
{ userId, sessionId },
REFRESH_TOKEN_SECRET,
{ expiresIn: '7d' }
);

### Authorization: Role-Based Access Control (RBAC)

const PERMISSIONS = {
CASHIER: ['transactions:create', 'transactions:read'],
MANAGER: ['transactions:*', 'reports:read', 'inventory:read'],
ADMIN: ['*']
} as const;

function checkPermission(user: User, action: string): boolean {
const userPermissions = PERMISSIONS[user.role];
return userPermissions.some(p =>
p === '*'

p === action
p.endsWith(':*') && action.startsWith(p.slice(0, -1))
);
}

### Audit Trail: Immutable Activity Logs

Every sensitive operation is logged to an append-only table:

CREATE TABLE audit_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL,
user_id UUID NOT NULL,
action VARCHAR(100) NOT NULL,
entity_type VARCHAR(50) NOT NULL,
entity_id UUID,
old_values JSONB,
new_values JSONB,
ip_address INET,
created_at TIMESTAMPTZ DEFAULT NOW()
);

-- No UPDATE or DELETE allowed
REVOKE UPDATE, DELETE ON audit_logs FROM app_user;


Deployment and Operations


### CI/CD Pipeline

# GitHub Actions workflow
name: Deploy Production

on:
push:
branches: [main]

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Run tests
run: pnpm test

- name: Build container
run: docker build -t gcr.io/polaris-prod/app:${{ github.sha }} .

- name: Deploy to Cloud Run
run: |
gcloud run deploy polaris-api \
--image gcr.io/polaris-prod/app:${{ github.sha }} \
--region asia-south1 \
--no-traffic

# Gradual rollout
gcloud run services update-traffic polaris-api \
--to-revisions ${{ github.sha }}=10

# Wait and verify
sleep 60
./scripts/health-check.sh

# Full rollout
gcloud run services update-traffic polaris-api \
--to-latest

### Monitoring Stack

  • Metrics: Google Cloud Monitoring with custom dashboards

  • Logs: Cloud Logging with structured JSON logs

  • Alerts: PagerDuty integration for critical issues

  • Uptime: 99.9% SLA with automated failover

Lessons Learned


  • Start with strong data isolation. RLS from day one saved us from countless potential bugs.
    • AI should augment, not replace. The best features combine AI suggestions with human confirmation.
    • Optimize for the 90% case. Most transactions are simple. Complex edge cases can have slower paths.
    • Urdu support matters. For Pakistani businesses, local language AI is a differentiator.
    • Cost efficiency enables adoption. Our PKR 15-75K/month pricing opened markets that SAP never could.

    What's Next


    We're currently working on:

    • Voice interface: Full voice control for hands-free operation

    • Predictive ordering: Automatic reorder suggestions based on demand forecasting

    • Multi-store analytics: Consolidated reporting across locations

    Interested in the technology behind Polaris? [Visit polariserp.app](https://polariserp.app) or [contact us](/contact) to learn more.

    Tags

    Polaris ERPArchitectureScalingAIReal-time

    Need Help Implementing This?

    Our team specializes in building production-grade AI systems. Let's discuss how we can help with your project.

    Schedule a Consultation