FinTech transparency at scale
HiddenFees.ae
A consumer-first platform that surfaces hidden charges across financial products and turns opaque fee structures into auditable, comparable data.
Visit HiddenFees.aeOutcomes
- Normalized fee schemas across heterogeneous bank disclosures
- Sub-second comparison queries via materialized views
- Consumer-grade UX with engineering-grade data integrity
1.1
Domain-driven fee normalization
- Problem
- Each institution exposes fees in different formats: flat JSON, PDF tables and legacy CSV. Without a canonical model, comparisons break down.
- Decision
- We introduced a FeeDescriptor pipeline: ingest → validate → map to a shared ontology → persist with provenance metadata for audit trails.
interface FeeDescriptor {
id: string;
category: "account" | "transfer" | "fx" | "card";
amount: { value: number; currency: string };
frequency: "once" | "monthly" | "annual";
source: { institutionId: string; capturedAt: string };
}
export async function normalizeFeePayload(
raw: unknown,
institutionId: string,
): Promise<FeeDescriptor> {
const parsed = FeeSchema.parse(raw);
return {
id: createFeeId(parsed),
category: mapCategory(parsed.type),
amount: { value: parsed.amount, currency: parsed.currency },
frequency: parsed.billingCycle,
source: { institutionId, capturedAt: new Date().toISOString() },
};
}1.2
Comparison API with cached aggregates
- Problem
- Real-time aggregation across millions of fee rows caused p95 latency spikes during peak traffic.
- Decision
- Materialized views refresh on a schedule; the public API reads from pre-computed comparison snapshots with stale-while-revalidate.
export async function GET(req: Request) {
const { productIds } = parseQuery(req);
const cacheKey = `compare:${productIds.sort().join(",")}`;
const cached = await redis.get(cacheKey);
if (cached) {
return Response.json(JSON.parse(cached), {
headers: { "X-Cache": "HIT" },
});
}
const snapshot = await db.comparisonSnapshot.findMany({
where: { productId: { in: productIds } },
});
await redis.setex(cacheKey, 300, JSON.stringify(snapshot));
return Response.json(snapshot, { headers: { "X-Cache": "MISS" } });
}