Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions services/trendAnalyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export class TrendAnalyzer {
});
}

public close() {
this.db.close();
public close(callback?: (err: Error | null) => void) {
this.db.close(callback);
}
}
53 changes: 53 additions & 0 deletions tests/adaptiveWeights.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { AdaptiveWeights } from '../services/adaptiveWeights';
import * as fs from 'fs';
import * as path from 'path';

describe('AdaptiveWeights', () => {
const testDir = path.join(__dirname, 'testData_adaptive');
const weightsFile = path.join(testDir, 'modelWeights.json');

beforeEach(() => {
if (fs.existsSync(testDir)) {
fs.rmSync(testDir, { recursive: true, force: true });
}
});

afterAll(() => {
if (fs.existsSync(testDir)) {
fs.rmSync(testDir, { recursive: true, force: true });
}
});

it('should initialize with default weights if file does not exist', () => {
const adaptive = new AdaptiveWeights('tests/testData_adaptive/modelWeights.json');
const newWeights = adaptive.optimizeWeights({}, 0);
expect(newWeights).toEqual({ default: 1.0 });
expect(fs.existsSync(weightsFile)).toBeTruthy();
});

it('should boost category weight if it represents > 10% of total volume', () => {
const adaptive = new AdaptiveWeights('tests/testData_adaptive/modelWeights.json');
const newWeights = adaptive.optimizeWeights({ 'pothole': 20, 'water': 5 }, 100);

expect(newWeights['pothole']).toBe(1.1); // > 10%
expect(newWeights['water']).toBeUndefined(); // <= 10%
});

it('should not boost category weight if it represents exactly 10% of total volume', () => {
const adaptive = new AdaptiveWeights('tests/testData_adaptive/modelWeights.json');
const newWeights = adaptive.optimizeWeights({ 'pothole': 10 }, 100);

expect(newWeights['pothole']).toBeUndefined(); // exactly 10%
});


it('should read existing weights from file', () => {
fs.mkdirSync(testDir, { recursive: true });
fs.writeFileSync(weightsFile, JSON.stringify({ current: { 'pothole': 1.5 }, history: [] }));

const adaptive = new AdaptiveWeights('tests/testData_adaptive/modelWeights.json');
const newWeights = adaptive.optimizeWeights({ 'pothole': 15 }, 100); // 15% -> boost by 0.1

expect(newWeights['pothole']).toBe(1.6);
});
});
31 changes: 20 additions & 11 deletions tests/dailyRefinement.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ describe('Daily Civic Intelligence Refinement Engine', () => {
stmt.run('Water supply is completely broken', 'water', 'Ward 2', now);
stmt.run('Another pothole here', 'infrastructure', 'Ward 1', now);

stmt.finalize(done);
stmt.finalize((finalizeErr) => {
db.close((closeErr) => done(finalizeErr ?? closeErr));
});
});
});

Expand All @@ -35,16 +37,23 @@ describe('Daily Civic Intelligence Refinement Engine', () => {

it('should correctly analyze trends in the last 24 hours', async () => {
analyzer = new TrendAnalyzer(dbPath);
const results = await analyzer.analyzeLast24Hours();

expect(results.totalIssues).toBe(3);
expect(results.categorySpikes['infrastructure']).toBe(2);
expect(results.categorySpikes['water']).toBe(1);
expect(results.topKeywords).toContain('pothole');
expect(results.locations).toContain('Ward 1');
expect(results.locations).toContain('Ward 2');

analyzer.close();
try {
const results = await analyzer.analyzeLast24Hours();

expect(results.totalIssues).toBe(3);
expect(results.categorySpikes['infrastructure']).toBe(2);
expect(results.categorySpikes['water']).toBe(1);
expect(results.topKeywords).toContain('pothole');
expect(results.locations).toContain('Ward 1');
expect(results.locations).toContain('Ward 2');
} finally {
await new Promise((resolve, reject) => {
analyzer.close((err) => {
if (err) reject(err);
else resolve(undefined);
});
});
}
});
});

Expand Down
83 changes: 83 additions & 0 deletions tests/intelligenceIndex.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { IntelligenceIndex } from '../services/intelligenceIndex';
import * as fs from 'fs';
import * as path from 'path';

describe('IntelligenceIndex', () => {
const testDir = path.join(__dirname, 'testData_intelligence');
const historyFile = path.join(testDir, 'intelligenceHistory.json');

beforeEach(() => {
if (fs.existsSync(testDir)) {
fs.rmSync(testDir, { recursive: true, force: true });
}
});

afterAll(() => {
if (fs.existsSync(testDir)) {
fs.rmSync(testDir, { recursive: true, force: true });
}
});

it('should calculate initial index properly', () => {
const indexer = new IntelligenceIndex('tests/testData_intelligence/intelligenceHistory.json');
const result = indexer.calculateDailyIndex(100, ['road'], { 'pothole': 10 }, ['Mumbai']);

expect(result.index).toBe(50.0); // Base 50, no mod for 100 issues
expect(result.diff).toBe(0.0);
expect(result.topConcern).toBe('pothole');
expect(result.highestSeverityRegion).toBe('Mumbai');
expect(fs.existsSync(historyFile)).toBeTruthy();
});

it('should increase score for low issue count (< 50)', () => {
const indexer = new IntelligenceIndex('tests/testData_intelligence/intelligenceHistory.json');
const result = indexer.calculateDailyIndex(30, [], {}, []);

expect(result.index).toBe(55.0);
expect(result.diff).toBe(5.0);
expect(result.topConcern).toBe('None');
expect(result.highestSeverityRegion).toBe('Unknown');
});

it('should decrease score for high issue count (> 200)', () => {
const indexer = new IntelligenceIndex('tests/testData_intelligence/intelligenceHistory.json');
const result = indexer.calculateDailyIndex(250, [], {}, []);

expect(result.index).toBe(40.0);
expect(result.diff).toBe(-10.0);
});

it('should not change score for exactly 50 issues', () => {
const indexer = new IntelligenceIndex('tests/testData_intelligence/intelligenceHistory.json');
const result = indexer.calculateDailyIndex(50, [], {}, []);

expect(result.index).toBe(50.0);
expect(result.diff).toBe(0.0);
});

it('should not change score for exactly 200 issues', () => {
const indexer = new IntelligenceIndex('tests/testData_intelligence/intelligenceHistory.json');
const result = indexer.calculateDailyIndex(200, [], {}, []);

expect(result.index).toBe(50.0);
expect(result.diff).toBe(0.0);
});

it('should use top keyword if category spike top concern is unknown', () => {
const indexer = new IntelligenceIndex('tests/testData_intelligence/intelligenceHistory.json');
const result = indexer.calculateDailyIndex(100, ['flooding'], { 'unknown': 20 }, []);

expect(result.topConcern).toBe('flooding');
});

it('should correctly read previous history', () => {
fs.mkdirSync(testDir, { recursive: true });
fs.writeFileSync(historyFile, JSON.stringify([{ date: '2026-07-25', index: 60.0 }]));

const indexer = new IntelligenceIndex('tests/testData_intelligence/intelligenceHistory.json');
const result = indexer.calculateDailyIndex(30, [], {}, []);

expect(result.index).toBe(65.0); // 60 + 5
expect(result.diff).toBe(5.0);
});
});
28 changes: 28 additions & 0 deletions tests/priorityEngine.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { PriorityEngine } from '../services/priorityEngine';

describe('PriorityEngine', () => {
let engine: PriorityEngine;

beforeEach(() => {
engine = new PriorityEngine();
});

it('should return base threshold of 0.85 for issues <= 100', () => {
expect(engine.adjustDuplicateThreshold(50)).toBe(0.85);
expect(engine.adjustDuplicateThreshold(100)).toBe(0.85);
});

it('should return threshold of 0.80 for issues > 100 and <= 500', () => {
expect(engine.adjustDuplicateThreshold(150)).toBe(0.80);
expect(engine.adjustDuplicateThreshold(500)).toBe(0.80);
});

it('should return threshold of 0.75 for issues > 500 and <= 1000', () => {
expect(engine.adjustDuplicateThreshold(750)).toBe(0.75);
expect(engine.adjustDuplicateThreshold(1000)).toBe(0.75);
});

it('should return threshold of 0.70 for issues > 1000', () => {
expect(engine.adjustDuplicateThreshold(1500)).toBe(0.70);
});
});
Loading