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: 300Result: 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 = 5Result: 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''paid';
'partial'
}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 Productionon:
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
- 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.