- Vision & Mission
- System Architecture
- Key Features
- Technology Stack
- Installation & Setup
- Implementation Details
- Database Schema
- API Documentation
- Future Integrations
- Challenges & Solutions
- Contributing
- Team & Credits
- License
- Contact
"Politicians told us to follow the money. So we built a map." โ Ian Kuria, Lead Developer
Billions vanish from Kenya's budget each year. We're the flashlight app for your tax shillings.
A Kenya where government budgets are as closely watched as Premier League matches, and where accountability isn't just a campaign slogan.
Turn boring government data into tools that bite. Make transparency irresistible, corruption expensive, and citizen engagement the national sport.
TruthKenya employs a modern, scalable architecture to deliver real-time insights into government spending and actions.
graph TD
A[Client Application] --> B[API Gateway]
B --> C[Authentication Service]
B --> D[Project Tracking Service]
B --> E[Leader Accountability Service]
B --> F[Budget Analysis Service]
B --> G[Report Management Service]
B --> H[Notification Service]
C & D & E & F & G & H --> I[Data Layer]
I --> J[(PostgreSQL - Structured Data)]
I --> K[(MongoDB - Unstructured Data)]
L[External APIs] --> B
B --> M[AI Service]
M --> N[OpenAI Integration]
H --> O[SMS/Email Service]
Our microservices architecture ensures:
- ๐ Scalability: Each service can scale independently based on demand
- ๐ Security: Isolated services reduce vulnerability surface area
- ๐ Deployment: Continuous deployment with minimal downtime
- ๐ Integration: Seamless connection with external data sources
The TruthKenya dashboard provides citizens with at-a-glance insights into Kenya's financial health and government performance.
// Dashboard KPI Component
export const EconomicIndicators = () => {
const { data, isLoading } = useDashboardStats();
return (
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<StatCard
title="Government Debt"
value={`KES ${data?.governmentDebt}T`}
trend={+2.4}
description="KES 295,432 per Citizen"
color="rose"
/>
<StatCard
title="Annual Budget"
value={`KES ${data?.annualBudget}T`}
trend={-0.8}
description="FY 2024/2025"
color="blue"
/>
<StatCard
title="Unemployment Rate"
value={`${data?.unemploymentRate}%`}
trend={+1.2}
description="7.7M people without jobs"
color="amber"
/>
</div>
);
};Translating abstract corruption figures into tangible impacts helps citizens understand what's at stake.
// Corruption Impact Component
export const ImpactCalculator = ({ amount }) => {
const schools = Math.round(amount / 35000000); // Avg primary school cost
const hospitals = Math.round(amount / 120000000); // Avg health center cost
const roads = Math.round(amount / 80000000 * 1000); // Cost per km of road
return (
<div className="p-6 bg-red-50 border border-red-200 rounded-lg">
<h3 className="text-xl font-bold mb-4">What KES {formatNumber(amount)} could have built:</h3>
<div className="grid grid-cols-3 gap-4 text-center">
<ImpactCard icon="๐ฅ" value={hospitals} label="Hospitals" />
<ImpactCard icon="๐ซ" value={schools} label="Schools" />
<ImpactCard icon="๐ฃ๏ธ" value={roads} label="KM of Roads" />
</div>
</div>
);
};Monitor government projects from initiation to completion with citizen reporting capabilities.
// Project Card Component
export const ProjectCard = ({ project }) => {
return (
<div className="border rounded-lg overflow-hidden shadow-sm hover:shadow-md transition-shadow">
<img
src={project.imageUrl || '/images/project-placeholder.jpg'}
alt={project.name}
className="h-48 w-full object-cover"
/>
<div className="p-4">
<h3 className="font-bold text-lg">{project.name}</h3>
<p className="text-gray-600 text-sm mb-3">{project.description}</p>
<div className="flex items-center mb-2">
<MapPinIcon className="h-4 w-4 text-gray-500 mr-1" />
<span className="text-sm">{project.location}</span>
</div>
<div className="flex justify-between mb-4">
<div>
<p className="text-sm text-gray-500">Budget</p>
<p className="font-medium">KES {formatNumber(project.budget)}</p>
</div>
<div>
<p className="text-sm text-gray-500">Deadline</p>
<p className="font-medium">{formatDate(project.deadline)}</p>
</div>
</div>
<div className="mb-3">
<div className="flex justify-between mb-1">
<span className="text-sm">Completion</span>
<span className="text-sm font-medium">{project.completionPercentage}%</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className="bg-blue-600 h-2 rounded-full"
style={{ width: `${project.completionPercentage}%` }}
></div>
</div>
</div>
<div className="flex justify-between items-center">
<div className="flex">
{[1, 2, 3, 4, 5].map((star) => (
<StarIcon
key={star}
className={`h-4 w-4 ${star <= project.rating ? 'text-yellow-400' : 'text-gray-300'}`}
/>
))}
<span className="text-sm ml-1">{project.rating.toFixed(1)}</span>
</div>
<Button size="sm">View Details</Button>
</div>
</div>
</div>
);
};Track elected officials' performance with data-driven metrics and citizen ratings.
// Leader Profile Component
export const LeaderProfile = ({ leader }) => {
const {
name, position, county, imageUrl,
projectsCompleted, projectsOngoing,
attendanceRate, fundsUtilized, fundsAllocated,
approvalRating
} = leader;
return (
<div className="flex flex-col md:flex-row gap-6 p-6 bg-white rounded-lg shadow">
<div className="md:w-1/4 flex flex-col items-center">
<img
src={imageUrl || '/images/leader-placeholder.jpg'}
alt={name}
className="w-32 h-32 rounded-full object-cover mb-4"
/>
<h3 className="font-bold text-xl text-center">{name}</h3>
<p className="text-gray-600 text-center mb-1">{position}</p>
<p className="text-gray-500 text-sm text-center">{county} County</p>
</div>
<div className="md:w-3/4">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
<StatCard label="Projects Completed" value={projectsCompleted} />
<StatCard label="Projects Ongoing" value={projectsOngoing} />
<StatCard label="Attendance Rate" value={`${attendanceRate}%`} />
</div>
<div className="mb-6">
<h4 className="font-medium mb-2">Funds Utilization</h4>
<div className="flex justify-between mb-1">
<span className="text-sm">KES {formatNumber(fundsUtilized)}</span>
<span className="text-sm">KES {formatNumber(fundsAllocated)}</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className="bg-green-600 h-2 rounded-full"
style={{ width: `${(fundsUtilized/fundsAllocated) * 100}%` }}
></div>
</div>
</div>
<div className="mb-6">
<h4 className="font-medium mb-2">Public Approval Rating</h4>
<div className="flex items-center gap-4">
<div className="text-3xl font-bold">{approvalRating.toFixed(1)}</div>
<div className="flex-1">
<ApprovalRatingStars rating={approvalRating} />
</div>
</div>
</div>
<div className="flex gap-3">
<Button>Vote Again</Button>
<Button variant="outline" className="text-red-500 border-red-300">
Impeach
</Button>
</div>
</div>
</div>
);
};An intelligent chatbot helping citizens understand complex governance and economic topics.
// AI Chatbot Component
export const CivicsBot = () => {
const [query, setQuery] = useState('');
const [messages, setMessages] = useState([
{
role: 'assistant',
content: 'Hello! I'm TruthBot. Ask me anything about Kenya's government, budgets, or civic processes.'
}
]);
const [isLoading, setIsLoading] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
if (!query.trim()) return;
// Add user message
setMessages(prev => [...prev, { role: 'user', content: query }]);
setIsLoading(true);
try {
const response = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
messages: [...messages, { role: 'user', content: query }]
})
});
const data = await response.json();
setMessages(prev => [...prev, { role: 'assistant', content: data.content }]);
} catch (error) {
console.error('Error fetching response:', error);
setMessages(prev => [...prev, {
role: 'assistant',
content: 'Sorry, I encountered an error. Please try again.'
}]);
} finally {
setIsLoading(false);
setQuery('');
}
};
return (
<div className="border rounded-lg overflow-hidden shadow-md h-[500px] flex flex-col">
<div className="bg-blue-600 text-white p-3">
<h3 className="font-bold">TruthBot - Civic Education Assistant</h3>
</div>
<div className="flex-1 p-4 overflow-y-auto bg-gray-50">
{messages.map((msg, index) => (
<div
key={index}
className={`mb-3 ${msg.role === 'assistant' ? 'pl-2' : 'pl-10'}`}
>
<div
className={`p-3 rounded-lg ${
msg.role === 'assistant'
? 'bg-white border'
: 'bg-blue-500 text-white'
}`}
>
{msg.content}
</div>
</div>
))}
{isLoading && (
<div className="pl-2 mb-3">
<div className="p-3 rounded-lg bg-white border">
<div className="flex gap-2">
<div className="w-2 h-2 bg-gray-300 rounded-full animate-bounce" />
<div className="w-2 h-2 bg-gray-300 rounded-full animate-bounce" style={{ animationDelay: '0.2s' }} />
<div className="w-2 h-2 bg-gray-300 rounded-full animate-bounce" style={{ animationDelay: '0.4s' }} />
</div>
</div>
</div>
)}
</div>
<form onSubmit={handleSubmit} className="p-3 border-t bg-white">
<div className="flex gap-2">
<input
type="text"
value={query}
onChange={e => setQuery(e.target.value)}
placeholder="Ask about Kenya's governance..."
className="flex-1 border rounded-md p-2"
/>
<Button type="submit" disabled={isLoading}>
{isLoading ? <Spinner size="sm" /> : <SendIcon className="h-4 w-4" />}
</Button>
</div>
</form>
</div>
);
};TruthKenya leverages cutting-edge technologies to deliver a robust, scalable platform:
- React 18 with Next.js 13: For server-side rendering and optimal SEO
- Tailwind CSS: For responsive, utility-first styling
- D3.js & Recharts: For dynamic data visualizations
- ShadcnUI: For consistent, accessible UI components
- React Query: For efficient data fetching and caching
- Node.js: Runtime environment
- Express.js: API framework
- Prisma ORM: Type-safe database queries
- JWT: For secure authentication
- OpenAI API: For AI-powered civic education
- Twilio: For SMS notifications
- PostgreSQL: Primary database for structured data
- MongoDB: For unstructured data (citizen reports, images)
- Redis: For caching and rate limiting
- Docker & Kubernetes: For containerization and orchestration
- GitHub Actions: For CI/CD pipelines
- AWS: For cloud infrastructure
- Terraform: For infrastructure as code
- Jest & React Testing Library: For unit and integration tests
- Cypress: For end-to-end testing
- ESLint & Prettier: For code quality and consistency
Get TruthKenya up and running in your local environment:
- Node.js v16+
- PostgreSQL 14+
- MongoDB 5+
- Redis 6+
- Clone the repository
git clone https://github.com/truthkenya/platform.git
cd platform- Install dependencies
# Install frontend dependencies
cd client
npm install
# Install backend dependencies
cd ../server
npm install- Set up environment variables
# In the server directory
cp .env.example .env
# Edit .env with your database credentials and API keys- Set up the database
# In the server directory
npx prisma migrate dev
npx prisma db seed- Start the development servers
# Start the backend server (from server directory)
npm run dev
# In a new terminal, start the frontend (from client directory)
npm run dev- Access the application
- Frontend: http://localhost:3000
- API: http://localhost:8000
TruthKenya implements a secure authentication system using JWT with refresh tokens:
sequenceDiagram
participant User
participant Client
participant API
participant DB
User->>Client: Enter credentials
Client->>API: POST /auth/login
API->>DB: Validate credentials
DB->>API: Return user data
API->>Client: Send JWT + Refresh Token
Client->>User: Login successful
Note over Client: Store tokens
Client->>API: Request with JWT
API->>API: Verify JWT
alt JWT valid
API->>Client: Return requested data
else JWT expired
API->>Client: 401 Unauthorized
Client->>API: POST /auth/refresh (with Refresh Token)
API->>DB: Validate Refresh Token
DB->>API: Token valid
API->>Client: New JWT + Refresh Token
Client->>API: Retry original request
API->>Client: Return requested data
end
We use WebSockets for real-time updates on project status changes and citizen reports:
// server/services/websocket.js
const { Server } = require('socket.io');
module.exports = (httpServer) => {
const io = new Server(httpServer, {
cors: {
origin: process.env.CLIENT_URL,
methods: ["GET", "POST"]
}
});
io.on('connection', (socket) => {
console.log('New client connected');
// Join room for specific county updates
socket.on('joinCounty', (countyId) => {
socket.join(`county-${countyId}`);
});
// Join room for specific project updates
socket.on('joinProject', (projectId) => {
socket.join(`project-${projectId}`);
});
// Listen for new reports
socket.on('submitReport', async (report) => {
try {
// Save report to database
const savedReport = await reportService.create(report);
// Notify admins
io.to('admins').emit('newReport', savedReport);
// Notify users following this project
io.to(`project-${report.projectId}`).emit('projectUpdate', {
type: 'newReport',
data: savedReport
});
// Notify users in this county
io.to(`county-${report.countyId}`).emit('countyUpdate', {
type: 'newReport',
data: savedReport
});
} catch (error) {
socket.emit('error', { message: 'Failed to submit report' });
}
});
socket.on('disconnect', () => {
console.log('Client disconnected');
});
});
return io;
};TruthKenya implements a hybrid approach to data visualization:
- Server-side data preparation: Complex aggregations are performed on the server
- Client-side rendering: D3.js and Recharts render visualizations client-side
- Progressive enhancement: Basic charts work without JavaScript, enhanced with interactivity when available
// client/components/charts/BudgetBreakdown.jsx
import { ResponsiveContainer, PieChart, Pie, Cell, Tooltip, Legend } from 'recharts';
export const BudgetBreakdown = ({ data }) => {
const COLORS = ['#FF8042', '#0088FE', '#00C49F', '#FFBB28', '#FF00FF', '#808080'];
return (
<div className="h-72">
<h3 className="text-lg font-medium mb-3">Budget Allocation</h3>
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={data}
cx="50%"
cy="50%"
labelLine={false}
outerRadius={80}
fill="#8884d8"
dataKey="value"
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
>
{data.map((entry, index) => (
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
))}
</Pie>
<Tooltip formatter={(value) => `KES ${value.toLocaleString()}`} />
<Legend />
</PieChart>
</ResponsiveContainer>
</div>
);
};TruthKenya uses a normalized database design to efficiently store and retrieve data:
erDiagram
User {
int id PK
string email
string hashedPassword
string firstName
string lastName
string role
timestamp createdAt
timestamp updatedAt
}
County {
int id PK
string name
string code
polygon boundaries
}
Project {
int id PK
string name
string description
decimal budget
int countyId FK
int sectorId FK
date startDate
date endDate
float completionPercentage
timestamp createdAt
timestamp updatedAt
}
Sector {
int id PK
string name
string code
}
Leader {
int id PK
string name
string position
int countyId FK
date electionDate
date termEnd
float approvalRating
int attendanceRate
timestamp createdAt
timestamp updatedAt
}
CitizenReport {
int id PK
int userId FK
int projectId FK
string title
string description
string status
point location
array images
timestamp createdAt
timestamp updatedAt
}
Budget {
int id PK
int fiscalYearId FK
int countyId FK
int sectorId FK
decimal allocation
decimal expenditure
timestamp createdAt
timestamp updatedAt
}
FiscalYear {
int id PK
string name
date startDate
date endDate
}
ImpeachmentPetition {
int id PK
int leaderId FK
string reason
int signatureCount
string status
timestamp createdAt
timestamp updatedAt
}
User ||--o{ CitizenReport : submits
County ||--o{ Project : has
County ||--o{ Leader : has
County ||--o{ Budget : receives
Sector ||--o{ Project : categorizes
Sector ||--o{ Budget : allocates
Project ||--o{ CitizenReport : receives
Leader ||--o{ ImpeachmentPetition : targets
FiscalYear ||--o{ Budget : contains
Our database design principles include:
- Normalization: Reduces data redundancy
- Indexing: Optimizes query performance
- Temporal tracking: Historical data preservation
- Spatial support: Geolocation for projects and reports
TruthKenya exposes a RESTful API with the following key endpoints:
POST /api/auth/register- Register a new userPOST /api/auth/login- Login and receive tokensPOST /api/auth/refresh- Refresh access tokenPOST /api/auth/logout- Invalidate tokens
GET /api/projects- List all projects (with filtering)GET /api/projects/:id- Get project detailsPOST /api/projects/:id/reports- Submit a citizen reportGET /api/projects/:id/reports- Get reports for a project
GET /api/leaders- List all leaders (with filtering)GET /api/leaders/:id- Get leader profilePOST /api/leaders/:id/ratings- Submit leader ratingPOST /api/leaders/:id/impeachment- Submit impeachment signature
GET /api/budgets- Get budget allocations (with filtering)GET /api/budgets/fiscal-years- List fiscal yearsGET /api/budgets/sectors- List budget sectors
GET /api/counties- List all countiesGET /api/counties/:id- Get county detailsGET /api/counties/:id/projects- Get county projectsGET /api/counties/:id/leaders- Get county leaders
GET /api/reports- List citizen reports (with filtering)POST /api/reports- Create a new reportGET /api/reports/:id- Get report details
GET /api/analytics/dashboard- Get dashboard statisticsGET /api/analytics/corruption- Get corruption statisticsGET /api/analytics/performance- Get performance rankings
All API endpoints include:
- Pagination support
- Filtering capabilities
- Sorting options
- Proper error handling
- Rate limiting
TruthKenya has an ambitious roadmap for expanding functionality:
A dedicated mobile application to enable offline data collection and wider reach, especially in rural areas with limited connectivity.
Adding support for Swahili and other local languages to increase accessibility across Kenya.
Partnership with satellite imagery providers to monitor infrastructure projects and verify completion claims.
Integration with e-procurement systems to track government contracts and flag potential irregularities.
Sophisticated economic models to predict the impact of government policies on different demographic groups.
Implementation of blockchain technology for immutable record-keeping of government spending.
Automated tracking of news mentions related to corruption cases and government projects.
Allow users to subscribe to alerts for specific projects, leaders, or geographic areas.
Building TruthKenya has presented unique challenges:
Challenge: Government data is often inconsistent, outdated, or unavailable.
Solution: We've implemented a multi-source data collection approach combining official records, media reports, and verified citizen submissions.
Challenge: Protecting whistleblowers and preventing system abuse.
Solution: End-to-end encryption, anonymous reporting options, and robust verification processes.
Challenge: Many Kenyans have limited internet access.
Solution: Low-bandwidth mode, SMS integration, and offline capability for the future mobile app.
Challenge: Potential pushback from political entities.
Solution: Strict factual accuracy, non-partisan approach, and transparent methodology.
Challenge: Varying levels of digital literacy among citizens.
Solution: Intuitive UI, educational resources, and community training initiatives.
Challenge: Keeping information current across multiple data sources.
Solution: Automated data ingestion pipelines, scheduled updates, and crowdsourced verification.
We welcome contributions to TruthKenya! Here's how you can help:
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Commit changes:
git commit -am 'Add my feature' - Push to branch:
git push origin feature/my-feature - Submit a pull request
- Follow the code style and conventions
- Write tests for new features
- Update documentation as needed
- Respect the code of conduct
- ๐ฅ๏ธ Frontend Development
- ๐ API Integrations
- ๐ Data Visualization
- ๐ฑ Mobile Development
- ๐ Localization
- ๐ Documentation
TruthKenya is led by:
- Lead Developer & Architect
- Website: kuria.pro
- GitHub: @kuriabyte
- Email: ian@truthkenya.com
Ian Kuria
- Data Visualization
- Backend Development
- UI/UX Design
- Content & Research
- Kenya National Bureau of Statistics
- Transparency International Kenya
- Open Institute
TruthKenya is released under the MIT License.
- Website: truthkenya.com
- Email: info@truthkenya.com
- Twitter: @TruthKenya
- GitHub: github.com/truthkenya
Building a more transparent Kenya, one data point at a time.
#TruthMatters #OpenKenya #CitizenEmpowerment
