Implement Priority Engine for Civic Intelligence#513
Conversation
…c weights\n\n- Created `services/priorityEngine.ts` to calculate issue severity and urgency using historical category weights from `modelWeights.json`.\n- Added `tests/priorityEngine.test.ts` to verify score calculations and boundary conditions.\n- Verified all 11 unit tests pass.
|
👋 Jules, reporting for duty! I'm here to lend a hand with this pull request. When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down. I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job! For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
✅ Deploy Preview for fixmybharat canceled.
|
🙏 Thank you for your contribution, @RohanExploit!PR Details:
Quality Checklist:
Review Process:
Note: The maintainers will monitor code quality and ensure the overall project flow isn't broken. |
📝 WalkthroughWalkthroughA new PriorityEngine class has been added that calculates issue priority by evaluating severity keywords, applying adaptive category weight multipliers, and boosting scores based on upvotes. The implementation includes comprehensive test coverage demonstrating low, high, and critical severity classifications. Changes
Sequence DiagramsequenceDiagram
participant User
participant PriorityEngine
participant AdaptiveWeights
User->>PriorityEngine: calculatePriority(issue)
PriorityEngine->>PriorityEngine: Extract keywords & identify category
PriorityEngine->>PriorityEngine: Assign base severityScore<br/>(10, 40, 70, or 90)
PriorityEngine->>AdaptiveWeights: Load categoryWeights
AdaptiveWeights-->>PriorityEngine: Return weights for category
PriorityEngine->>PriorityEngine: Apply weight multiplier<br/>if weight > 1
PriorityEngine->>PriorityEngine: Derive severity label<br/>from severityScore thresholds
PriorityEngine->>PriorityEngine: Calculate urgencyScore<br/>from severityScore + upvotes<br/>(capped at 100)
PriorityEngine->>PriorityEngine: Compile reasoning array
PriorityEngine-->>User: Return {severity, severityScore,<br/>urgencyScore, reasoning}
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Adds a new PriorityEngine service to compute issue severity/urgency from keyword-based heuristics and AdaptiveWeights category multipliers, along with a Jest test suite validating several core scenarios.
Changes:
- Introduce
services/priorityEngine.tsto calculate severity/urgency scores + reasoning from issue descriptions, category weights, and upvotes. - Add
tests/priorityEngine.test.tscovering low/high/critical keyword paths and category-weight boosting behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| services/priorityEngine.ts | New PriorityEngine implementation applying keyword-based scoring and AdaptiveWeights category multipliers. |
| tests/priorityEngine.test.ts | New Jest tests validating scoring, labels, upvote urgency boost, and reasoning output. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| @@ -0,0 +1,77 @@ | |||
| import { Issue, ModelWeights } from './types'; | |||
There was a problem hiding this comment.
ModelWeights is imported but never used in this module. Removing the unused import will reduce noise and avoid unused-import lint failures if enabled later.
| import { Issue, ModelWeights } from './types'; | |
| import { Issue } from './types'; |
| let baseLabel = "Low"; | ||
|
|
||
| if (criticalKeywords.some(word => text.includes(word))) { | ||
| severityScore = 90; | ||
| baseLabel = "Critical"; | ||
| reasoning.push("Flagged as Critical due to critical keywords in description."); | ||
| } else if (highKeywords.some(word => text.includes(word))) { | ||
| severityScore = 70; | ||
| baseLabel = "High"; | ||
| reasoning.push("Flagged as High Severity due to high keywords in description."); | ||
| } else if (mediumKeywords.some(word => text.includes(word))) { | ||
| severityScore = 40; | ||
| baseLabel = "Medium"; |
There was a problem hiding this comment.
baseLabel is assigned in each keyword branch but is never read afterwards. This looks like leftover logic and can be removed, or (if you intended to surface the pre-weight label) returned explicitly to avoid dead code.
| let baseLabel = "Low"; | |
| if (criticalKeywords.some(word => text.includes(word))) { | |
| severityScore = 90; | |
| baseLabel = "Critical"; | |
| reasoning.push("Flagged as Critical due to critical keywords in description."); | |
| } else if (highKeywords.some(word => text.includes(word))) { | |
| severityScore = 70; | |
| baseLabel = "High"; | |
| reasoning.push("Flagged as High Severity due to high keywords in description."); | |
| } else if (mediumKeywords.some(word => text.includes(word))) { | |
| severityScore = 40; | |
| baseLabel = "Medium"; | |
| if (criticalKeywords.some(word => text.includes(word))) { | |
| severityScore = 90; | |
| reasoning.push("Flagged as Critical due to critical keywords in description."); | |
| } else if (highKeywords.some(word => text.includes(word))) { | |
| severityScore = 70; | |
| reasoning.push("Flagged as High Severity due to high keywords in description."); | |
| } else if (mediumKeywords.some(word => text.includes(word))) { | |
| severityScore = 40; |
| // 2. Apply Adaptive Category Weights | ||
| const currentWeights = this.adaptiveWeights.loadWeights(); | ||
| const categoryWeight = currentWeights.categoryWeights[issue.category]; |
There was a problem hiding this comment.
calculatePriority() calls adaptiveWeights.loadWeights() on every invocation. In the current implementation loadWeights() does synchronous filesystem I/O + JSON.parse, so calling this per issue can be a significant bottleneck and will also throw if the weights file is corrupted. Consider caching the loaded weights in PriorityEngine (with an explicit refresh) and/or wrapping the load in a try/catch with a safe fallback.
There was a problem hiding this comment.
🧹 Nitpick comments (4)
tests/priorityEngine.test.ts (2)
5-93: Consider adding tests for medium severity and edge cases.The existing tests provide good coverage of core paths. For completeness, consider adding:
- Medium severity test (with "maintenance", "crack", or "dirty" keywords)
- Empty/missing description handling
- Edge case where
severityScoreexactly equals threshold boundaries (40, 70, 90)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/priorityEngine.test.ts` around lines 5 - 93, Add unit tests in tests/priorityEngine.test.ts that cover medium severity, empty/missing description, and boundary cases for severity thresholds: create a test invoking PriorityEngine.calculatePriority with a description containing medium keywords (e.g., "maintenance", "crack", "dirty") and assert severityScore in the 40–69 range and severity === 'Medium'; add a test where Issue.description is empty or undefined to ensure calculatePriority handles it gracefully (no crash, returns a default/base severity and appropriate reasoning); and add tests where generated severityScore equals exactly 40, 70, and 90 (mock or craft inputs and category weights so calculatePriority produces those exact scores) asserting the resulting severity classifications match the expected boundaries. Ensure tests reference PriorityEngine and calculatePriority so they fail if behavior changes.
9-28: Consider using typed mocks for better type safety.The
as anyassertion on line 26 bypasses TypeScript's type checking. While this works, usingjest.Mocked<AdaptiveWeights>orjest.mock()with proper typing would catch interface mismatches at compile time.🧹 Alternative approach with typed mock
beforeEach(() => { const mockLoadWeights = jest.fn().mockReturnValue({ categoryWeights: { 'Pothole': 5, 'Garbage': 3, 'Water Supply': 4, 'Streetlight': 1, 'Flooding': 8 }, duplicateThreshold: 0.85, lastUpdated: new Date().toISOString(), history: [] } as ModelWeights); adaptiveWeights = { loadWeights: mockLoadWeights, optimizeWeights: jest.fn(), saveWeights: jest.fn(), configPath: 'mock-path' // include all required properties } as jest.Mocked<AdaptiveWeights>; priorityEngine = new PriorityEngine(adaptiveWeights); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/priorityEngine.test.ts` around lines 9 - 28, The test uses an unsafe "as any" cast for adaptiveWeights; replace it with a properly typed Jest mock by creating jest.fn() mocks for loadWeights, optimizeWeights, and saveWeights (e.g., const mockLoadWeights = jest.fn().mockReturnValue(<ModelWeights>({...}))), include any required properties on the AdaptiveWeights interface (e.g., configPath) and then assign adaptiveWeights = { loadWeights: mockLoadWeights, optimizeWeights: jest.fn(), saveWeights: jest.fn(), /* other props */ } as jest.Mocked<AdaptiveWeights>; finally pass that typed mock into new PriorityEngine(adaptiveWeights) so TypeScript catches interface mismatches and you can use jest assertions on the mock methods (referencing AdaptiveWeights, loadWeights, optimizeWeights, saveWeights, and PriorityEngine).services/priorityEngine.ts (2)
63-66: Minor: Redundant truthiness check onupvotes.The
issue.upvotes &&check is redundant sinceissue.upvotes > 0already evaluates tofalsefor0, negative numbers, and falsy values.🧹 Simplify condition
- if (issue.upvotes && issue.upvotes > 0) { + if (issue.upvotes > 0) { urgencyScore += Math.min(20, issue.upvotes * 2); // Cap upvote boost at 20 reasoning.push(`Urgency increased by upvotes (${issue.upvotes}).`); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@services/priorityEngine.ts` around lines 63 - 66, The condition in services/priorityEngine.ts redundantly checks issue.upvotes twice; replace the `if (issue.upvotes && issue.upvotes > 0)` with a single check `if (issue.upvotes > 0)` in the block that updates urgencyScore and reasoning (the code that increases urgencyScore by Math.min(20, issue.upvotes * 2) and pushes to reasoning) so the logic remains identical but simpler and clearer.
42-43: Consider caching weights to avoid repeated file I/O.
loadWeights()performs synchronous file reads (peradaptiveWeights.ts). Calling it on everycalculatePriorityinvocation creates unnecessary I/O overhead, especially when processing multiple issues in batch.Consider loading weights once (e.g., in constructor or via a lazy-initialized cache) and reloading only when explicitly requested or when the file changes.
♻️ Suggested approach
export class PriorityEngine { private adaptiveWeights: AdaptiveWeights; + private cachedWeights: ModelWeights | null = null; constructor(adaptiveWeights?: AdaptiveWeights) { this.adaptiveWeights = adaptiveWeights || new AdaptiveWeights(); } + private getWeights(): ModelWeights { + if (!this.cachedWeights) { + this.cachedWeights = this.adaptiveWeights.loadWeights(); + } + return this.cachedWeights; + } + + public refreshWeights(): void { + this.cachedWeights = null; + } public calculatePriority(issue: Issue): { severity: string; severityScore: number; urgencyScore: number; reasoning: string[] } { // ... - const currentWeights = this.adaptiveWeights.loadWeights(); + const currentWeights = this.getWeights();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@services/priorityEngine.ts` around lines 42 - 43, The current calculatePriority flow calls adaptiveWeights.loadWeights() on every invocation which causes repeated synchronous file I/O; modify the PriorityEngine class to cache weights (e.g., a private this.cachedWeights initialized in the constructor or via lazy getter) and have calculatePriority read from this.cachedWeights instead of calling loadWeights() each time, and add an explicit reloadWeights() method (or watch/refresh hook) that updates this.cachedWeights by calling adaptiveWeights.loadWeights() when needed; update references to currentWeights and categoryWeight to use the cached copy.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@services/priorityEngine.ts`:
- Around line 63-66: The condition in services/priorityEngine.ts redundantly
checks issue.upvotes twice; replace the `if (issue.upvotes && issue.upvotes >
0)` with a single check `if (issue.upvotes > 0)` in the block that updates
urgencyScore and reasoning (the code that increases urgencyScore by Math.min(20,
issue.upvotes * 2) and pushes to reasoning) so the logic remains identical but
simpler and clearer.
- Around line 42-43: The current calculatePriority flow calls
adaptiveWeights.loadWeights() on every invocation which causes repeated
synchronous file I/O; modify the PriorityEngine class to cache weights (e.g., a
private this.cachedWeights initialized in the constructor or via lazy getter)
and have calculatePriority read from this.cachedWeights instead of calling
loadWeights() each time, and add an explicit reloadWeights() method (or
watch/refresh hook) that updates this.cachedWeights by calling
adaptiveWeights.loadWeights() when needed; update references to currentWeights
and categoryWeight to use the cached copy.
In `@tests/priorityEngine.test.ts`:
- Around line 5-93: Add unit tests in tests/priorityEngine.test.ts that cover
medium severity, empty/missing description, and boundary cases for severity
thresholds: create a test invoking PriorityEngine.calculatePriority with a
description containing medium keywords (e.g., "maintenance", "crack", "dirty")
and assert severityScore in the 40–69 range and severity === 'Medium'; add a
test where Issue.description is empty or undefined to ensure calculatePriority
handles it gracefully (no crash, returns a default/base severity and appropriate
reasoning); and add tests where generated severityScore equals exactly 40, 70,
and 90 (mock or craft inputs and category weights so calculatePriority produces
those exact scores) asserting the resulting severity classifications match the
expected boundaries. Ensure tests reference PriorityEngine and calculatePriority
so they fail if behavior changes.
- Around line 9-28: The test uses an unsafe "as any" cast for adaptiveWeights;
replace it with a properly typed Jest mock by creating jest.fn() mocks for
loadWeights, optimizeWeights, and saveWeights (e.g., const mockLoadWeights =
jest.fn().mockReturnValue(<ModelWeights>({...}))), include any required
properties on the AdaptiveWeights interface (e.g., configPath) and then assign
adaptiveWeights = { loadWeights: mockLoadWeights, optimizeWeights: jest.fn(),
saveWeights: jest.fn(), /* other props */ } as jest.Mocked<AdaptiveWeights>;
finally pass that typed mock into new PriorityEngine(adaptiveWeights) so
TypeScript catches interface mismatches and you can use jest assertions on the
mock methods (referencing AdaptiveWeights, loadWeights, optimizeWeights,
saveWeights, and PriorityEngine).
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 1c1a2d2a-fbb1-4336-88b4-f48b4782a41f
📒 Files selected for processing (2)
services/priorityEngine.tstests/priorityEngine.test.ts
Implemented the missing
PriorityEnginemodule as part of the Daily Civic Intelligence Refinement Engine architecture. It correctly computes priority based on description keywords and dynamic category weights.PR created automatically by Jules for task 193245470447998047 started by @RohanExploit
Summary by cubic
Added PriorityEngine to score issue severity and urgency using keywords, adaptive category weights, and upvotes. This makes prioritization consistent and explainable in the Civic Intelligence pipeline.
Written for commit 36c0aa3. Summary will update on new commits.
Summary by CodeRabbit
Release Notes
New Features
Tests