Services Documentation
Service Overview
| Service | Location | Purpose |
|---|---|---|
| Firebase | lib/firebase.ts | Auth & database initialization |
| ML API Client | lib/ml-api/client.ts | ML model predictions |
| Blockchain Service | lib/blockchain/service.ts | On-chain storage |
| Reports Service | lib/reports/service.ts | Firestore CRUD |
| PDF Generators | lib/pdf/generators/ | Report PDF export |
Firebase Service
Initialization
Location: src/lib/firebase.ts
import { initializeApp, getApps } from "firebase/app";
import { getAuth } from "firebase/auth";
import { getFirestore } from "firebase/firestore";
const firebaseConfig = {
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
};
// Singleton initialization
const app = getApps().length === 0 ? initializeApp(firebaseConfig) : getApps()[0];
const auth = getAuth(app);
const db = getFirestore(app);
export { app, auth, db };
Usage
import { auth, db } from "@/lib/firebase";
import { signInWithEmailAndPassword } from "firebase/auth";
import { collection, addDoc } from "firebase/firestore";
// Auth
await signInWithEmailAndPassword(auth, email, password);
// Firestore
await addDoc(collection(db, "reports"), data);
ML API Client
MLAPIClient Class
Location: src/lib/ml-api/client.ts
Type-safe client for ML API communication.
class MLAPIClient {
private baseUrl: string;
private timeout: number;
private headers: Record<string, string>;
constructor(config?: MLClientConfig);
// Health & Status
async health(): Promise<HealthResponse>;
async ping(): Promise<boolean>;
async getModels(): Promise<ModelsResponse>;
async getSchema(modelId: "reusability" | "treatment"): Promise<SchemaResponse>;
// Reference Data
async getCPCBLimits(): Promise<CPCBLimitsResponse>;
async getClassInfo(): Promise<ClassInfoResponse>;
// Predictions
async predictReusability(input: WastewaterInput): Promise<ReusabilityResponse>;
async recommendTreatment(input: TreatmentInput, useCase?: TreatmentUseCase): Promise<TreatmentResponse>;
async batchPredict(request: BatchPredictionRequest): Promise<BatchResponse>;
// Reports
async generateReport(input: TreatmentInput): Promise<Blob>;
async downloadReport(input: TreatmentInput, filename?: string): Promise<void>;
}
Default Instance
// Singleton instance with default config
export const mlApi = new MLAPIClient();
// Convenience functions
export const checkHealth = () => mlApi.health();
export const predictReusability = (input) => mlApi.predictReusability(input);
export const recommendTreatment = (input, useCase?) => mlApi.recommendTreatment(input, useCase);
Custom Configuration
const customClient = new MLAPIClient({
baseUrl: "https://custom-api.example.com",
timeout: 60000,
headers: { "X-Custom-Header": "value" },
});
Error Handling
// Custom error classes
class MLAPIError extends Error {
constructor(message: string, public statusCode: number, public response?: APIErrorResponse);
}
class MLValidationError extends MLAPIError {
constructor(message: string, public details: ValidationError[]);
}
// Usage
try {
const result = await mlApi.predictReusability(input);
} catch (error) {
if (error instanceof MLValidationError) {
console.log("Validation errors:", error.details);
} else if (error instanceof MLAPIError) {
console.log("API error:", error.statusCode, error.message);
}
}
API Endpoints
| Endpoint | Method | Description |
|---|---|---|
/health | GET | API health status |
/api/models | GET | Available models list |
/api/schema/{model} | GET | Model input schema |
/api/cpcb-limits | GET | CPCB regulatory limits |
/api/class-info | GET | Reusability class info |
/api/predict/reusability | POST | Reusability prediction |
/api/predict/treatment | POST | Treatment recommendation |
/api/predict/batch | POST | Batch predictions |
/api/report/generate | POST | Generate PDF report |
/ws/reusability | WS | Real-time reusability |
/ws/treatment | WS | Real-time treatment |
WebSocket Service
Overview
The ML API provides WebSocket endpoints for real-time streaming predictions, ideal for SCADA system integration and continuous monitoring.
Frontend Page: src/app/(core)/websocket-monitor/page.tsx
WebSocket Endpoints
| Endpoint | Description |
|---|---|
/ws/reusability | Real-time reusability predictions |
/ws/treatment | Real-time treatment recommendations |
Connection
// Get WebSocket URL from environment
const wsBaseUrl = process.env.NEXT_PUBLIC_ML_API_URL?.replace("http", "ws") || "ws://localhost:8000";
// Connect to reusability endpoint
const reusabilityWs = new WebSocket(`${wsBaseUrl}/ws/reusability`);
// Connect to treatment endpoint
const treatmentWs = new WebSocket(`${wsBaseUrl}/ws/treatment`);
Message Protocol
Welcome Message (Server → Client)
Upon connection, the server sends a welcome message:
{
"type": "welcome",
"message": "Connected to reusability prediction WebSocket",
"timestamp": "2024-12-08T12:00:00Z"
}
Prediction Request (Client → Server)
// Reusability prediction request
const reusabilityMessage = {
type: "predict",
request_id: "scada-reuse-001", // Unique identifier for tracking
payload: {
flow_rate: 150,
influent_BOD: 45,
influent_COD: 120,
influent_TSS: 80,
influent_pH: 7.2,
influent_TDS: 800,
aeration_rate: 35,
chemical_dose: 12,
sludge_recycle_rate: 25,
retention_time: 8,
temperature: 28,
effluent_BOD: 15,
effluent_COD: 60,
effluent_TSS: 25,
effluent_TDS: 500,
effluent_pH: 7.0,
},
};
// Treatment prediction request
const treatmentMessage = {
type: "predict",
request_id: "scada-treat-001",
use_case: "Industrial Use", // Optional
payload: {
pH: 7.5,
TSS: 120,
Turbidity: 45,
BOD: 180,
COD: 350,
NH4_N: 25,
Total_Nitrogen: 40,
Phosphate: 8,
Fecal_Coliform: 5000,
Oil_Grease: 15,
TDS: 600,
Heavy_Metals: 0.5,
},
};
ws.send(JSON.stringify(message));
Prediction Response (Server → Client)
{
"ok": true,
"request_id": "scada-reuse-001",
"data": {
"prediction": "Class_A",
"prediction_display": "Class A",
"confidence_percent": 87.5,
"cpcb_compliance": {
"status": "PASS",
"violations": []
}
}
}
Error Response
{
"ok": false,
"request_id": "scada-reuse-001",
"error": "Invalid payload: missing required field 'pH'"
}
React Hook Example
function useWebSocket(model: "reusability" | "treatment") {
const [status, setStatus] = useState<"disconnected" | "connecting" | "connected" | "error">("disconnected");
const [lastMessage, setLastMessage] = useState<any>(null);
const wsRef = useRef<WebSocket | null>(null);
const connect = useCallback(() => {
const wsUrl = process.env.NEXT_PUBLIC_ML_API_URL?.replace("http", "ws") || "ws://localhost:8000";
const url = `${wsUrl}/ws/${model}`;
setStatus("connecting");
const ws = new WebSocket(url);
ws.onopen = () => setStatus("connected");
ws.onmessage = (event) => setLastMessage(JSON.parse(event.data));
ws.onerror = () => setStatus("error");
ws.onclose = () => setStatus("disconnected");
wsRef.current = ws;
}, [model]);
const send = useCallback((payload: any, requestId?: string) => {
if (wsRef.current?.readyState === WebSocket.OPEN) {
wsRef.current.send(
JSON.stringify({
type: "predict",
request_id: requestId || `req-${Date.now()}`,
payload,
})
);
}
}, []);
const disconnect = useCallback(() => {
wsRef.current?.close();
}, []);
return { status, lastMessage, connect, send, disconnect };
}
WebSocket Monitor Page
The frontend includes a dedicated WebSocket monitoring page at /websocket-monitor with:
- Connection Management: Connect/disconnect to both endpoints
- Stream Controls: Start/stop continuous data streaming
- Live Log Viewer: Real-time message log with expandable JSON
- Configurable Settings: Adjust stream interval, select models
- SCADA Simulation: Auto-generates realistic sensor data
SCADA Simulator (Python)
Location: ml/ws_scada_simulator.py
A Python script that simulates SCADA system integration:
# Install dependencies
pip install websockets
# Run simulator
python ws_scada_simulator.py
Features:
- Connects to both WebSocket endpoints
- Generates realistic sensor data
- Supports sequential or parallel streaming
- Configurable frame count and interval
Blockchain Service
BlockchainService Class
Location: src/lib/blockchain/service.ts
Server-side service for Polygon blockchain operations.
class BlockchainService {
private provider: JsonRpcProvider;
private wallet: Wallet | null;
private contract: Contract | null;
constructor();
// Status
isReady(): boolean;
getWalletAddress(): string;
async getBalance(): Promise<string>;
async getTotalReports(): Promise<number>;
async getStatus(): Promise<BlockchainStatus>;
// Storage
async storeReport(report: ReportData): Promise<StoreResult>;
async storeReportsBatch(reports: ReportData[]): Promise<StoreResult>;
// Verification
async verifyHash(reportHash: string): Promise<boolean>;
async verifyReport(report: ReportData): Promise<VerifyResult>;
// Helpers
getExplorerUrl(transactionHash: string): string;
getContractExplorerUrl(): string;
}
// Singleton instance
export const blockchainService = new BlockchainService();
Hash Generation
Location: src/lib/blockchain/hash.ts
// Generate deterministic hash for report
function generateReportHash(report: {
reportType: string;
userId: string;
input: Record<string, any>;
output: Record<string, any>;
createdAt: Date;
}): string;
// Hash user ID for privacy
function hashUserId(userId: string): string;
Configuration
Location: src/lib/blockchain/config.ts
export const BLOCKCHAIN_CONFIG = {
rpcUrl: process.env.NEXT_PUBLIC_POLYGON_RPC_URL || "https://rpc-amoy.polygon.technology",
chainId: 80002,
chainName: "Polygon Amoy Testnet",
contractAddress: process.env.NEXT_PUBLIC_CONTRACT_ADDRESS,
explorerUrl: "https://amoy.polygonscan.com",
};
export const CONTRACT_ABI = [
"function storeReport(string reportId, bytes32 reportHash, string reportType, bytes32 userIdHash)",
"function storeReportsBatch(string[] reportIds, bytes32[] reportHashes, string[] reportTypes, bytes32[] userIdHashes)",
"function verifyHash(bytes32 reportHash) view returns (bool)",
"function totalReports() view returns (uint256)",
];
API Routes
Location: src/app/api/blockchain/
| Route | Method | Description |
|---|---|---|
/api/blockchain/status | GET | Get blockchain status |
/api/blockchain/store | POST | Store report hash |
/api/blockchain/verify | POST | Verify report hash |
Reports Service
CRUD Operations
Location: src/lib/reports/service.ts
// Save a new report
async function saveReport(report: Omit<Report, "id">): Promise<string>;
// Get single report by ID
async function getReport(reportId: string, reportType?: ReportType): Promise<Report | null>;
// Get all reports for a user with filters
async function getReports(userId: string, filters?: ReportFilters): Promise<Report[]>;
// Get report summaries for list view
async function getReportSummaries(userId: string, filters?: ReportFilters): Promise<ReportSummary[]>;
// Delete a report
async function deleteReport(reportId: string, reportType?: ReportType): Promise<void>;
Report Type Mapping
// Each report type has its own Firestore collection
const REPORT_COLLECTIONS = {
prediction: "prediction_reports",
treatment: "treatment_reports",
"twin-engine": "twin_engine_reports",
"adaptive-optimizer": "adaptive_optimizer_reports",
};
function getCollectionName(reportType: ReportType): string {
return REPORT_COLLECTIONS[reportType];
}
Filters
interface ReportFilters {
reportType?: ReportType; // Filter by type
startDate?: Date; // Filter by date range
endDate?: Date;
limit?: number; // Limit results
}
// Usage
const reports = await getReports(userId, {
reportType: "prediction",
limit: 10,
});
Document Conversion
// Firestore document → Report object
function documentToReport(doc: DocumentData, id: string): Report {
return {
...doc,
id,
createdAt: doc.createdAt?.toDate() || new Date(),
};
}
// Report object → Firestore document
function reportToDocument(report: Omit<Report, "id">): DocumentData {
return {
...removeUndefinedValues(report),
createdAt: Timestamp.fromDate(report.createdAt),
};
}
PDF Generators
Prediction PDF
Location: src/lib/pdf/generators/prediction-pdf.ts
async function generatePredictionPDF(result: ReusabilityResult): Promise<void> {
// Generates and downloads PDF with:
// - Prediction summary
// - CPCB compliance table
// - Smart verdict
// - Treatment recommendations
// - Estimated values
}
Treatment PDF
Location: src/lib/pdf/generators/treatment-pdf.ts
async function generateTreatmentPDF(result: TreatmentResult): Promise<void> {
// Generates and downloads PDF with:
// - Treatment stage recommendation
// - Quality score
// - Treatment steps with descriptions
// - Parameter status
// - Chemical dosing suggestions
// - Risk assessment
}
PDF Templates
Location: src/lib/pdf/templates.ts
// Shared styles and utilities
const PDF_STYLES = {
headerColor: [40, 40, 40],
accentColor: [100, 100, 255],
tableHead: { fillColor: [40, 40, 40], textColor: [255, 255, 255] },
alternateRow: { fillColor: [245, 245, 245] },
};
function addHeader(doc: jsPDF, title: string): void;
function addTimestamp(doc: jsPDF): void;
function addTable(doc: jsPDF, data: any[], headers: string[]): void;
Usage
import { generatePredictionPDF } from "@/lib/pdf";
// After prediction completes
const result = await predictReusability(input);
await generatePredictionPDF(result);
// Downloads: prediction_report_TIMESTAMP.pdf
Service Patterns
Singleton Pattern
// Services are instantiated once
export const blockchainService = new BlockchainService();
export const mlApi = new MLAPIClient();
Error Handling
// Wrap operations in try-catch
async function saveReport(report) {
try {
const docRef = await addDoc(collection(db, collectionName), document);
return docRef.id;
} catch (error) {
console.error("Error saving report:", error);
throw new Error("Failed to save report");
}
}
Type Safety
// Strong typing for all service inputs/outputs
interface ReportData {
id: string;
reportType: ReportType;
userId: string;
input: Record<string, any>;
output: Record<string, any>;
createdAt: Date;
}
Last Updated: December 2024