-
Notifications
You must be signed in to change notification settings - Fork 36
Optimize Issue Data Structure and Async Performance #478
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,3 +20,5 @@ pydub | |
| googletrans==4.0.2 | ||
| langdetect | ||
| indic-nlp-library | ||
| pydantic | ||
| python-dotenv | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -114,7 +114,7 @@ async def create_issue( | |||||
| Issue.latitude <= max_lat, | ||||||
| Issue.longitude >= min_lon, | ||||||
| Issue.longitude <= max_lon | ||||||
| ).all() | ||||||
| ).limit(100).all() | ||||||
|
||||||
| ).limit(100).all() | |
| ).order_by(Issue.created_at.desc()).limit(100).all() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: Limiting the deduplication candidate query to 100 without ordering can skip nearby issues in dense areas, so duplicates may be created even when a matching issue exists within 50m.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/routers/issues.py, line 117:
<comment>Limiting the deduplication candidate query to 100 without ordering can skip nearby issues in dense areas, so duplicates may be created even when a matching issue exists within 50m.</comment>
<file context>
@@ -114,7 +114,7 @@ async def create_issue(
Issue.longitude >= min_lon,
Issue.longitude <= max_lon
- ).all()
+ ).limit(100).all()
)
</file context>
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,177 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import pytest | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import sys | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import hashlib | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from unittest.mock import MagicMock, AsyncMock, patch | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from pathlib import Path | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from fastapi.testclient import TestClient | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Setup environment | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| PROJECT_ROOT = Path(__file__).resolve().parents[2] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if str(PROJECT_ROOT) not in sys.path: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sys.path.insert(0, str(PROJECT_ROOT)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Mock heavy dependencies before importing main | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sys.modules['magic'] = MagicMock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sys.modules['telegram'] = MagicMock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sys.modules['telegram.ext'] = MagicMock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sys.modules['google'] = MagicMock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sys.modules['google.generativeai'] = MagicMock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sys.modules['transformers'] = MagicMock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Mock torch correctly for issubclass checks in scipy/sklearn | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class MockTensor: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pass | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mock_torch = MagicMock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mock_torch.Tensor = MockTensor | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sys.modules['torch'] = mock_torch | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sys.modules['speech_recognition'] = MagicMock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sys.modules['googletrans'] = MagicMock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sys.modules['langdetect'] = MagicMock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sys.modules['ultralytics'] = MagicMock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sys.modules['a2wsgi'] = MagicMock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sys.modules['firebase_functions'] = MagicMock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Mock pywebpush | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mock_pywebpush = MagicMock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mock_pywebpush.WebPushException = Exception | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sys.modules['pywebpush'] = mock_pywebpush | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import backend.main | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from backend.main import app | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from backend.models import Issue, Base | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from backend.database import get_db, engine | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from sqlalchemy import create_engine | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from sqlalchemy.orm import sessionmaker | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Use file-based SQLite for testing to ensure thread safety with run_in_threadpool | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TEST_DB_FILE = "test_issues.db" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if os.path.exists(TEST_DB_FILE): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| os.remove(TEST_DB_FILE) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SQLALCHEMY_DATABASE_URL = f"sqlite:///{TEST_DB_FILE}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| test_engine = create_engine( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=test_engine) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Override dependency | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def override_get_db(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| db = TestingSessionLocal() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| yield db | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| finally: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| db.close() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| app.dependency_overrides[get_db] = override_get_db | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: The test overrides Prompt for AI agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @pytest.fixture(scope="module") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def client(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Create tables | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Base.metadata.create_all(bind=test_engine) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| with patch("backend.main.create_all_ai_services") as mock_create: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mock_create.return_value = (AsyncMock(), AsyncMock(), AsyncMock()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Also patch RAG service retrieve to avoid errors | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| with patch("backend.routers.issues.rag_service.retrieve", return_value=None): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| with TestClient(app) as c: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| yield c | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Cleanup | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Base.metadata.drop_all(bind=test_engine) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if os.path.exists(TEST_DB_FILE): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| os.remove(TEST_DB_FILE) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+67
to
+85
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Clean up The override is applied globally and never removed, so subsequent tests can unintentionally keep using the test DB dependency. 🧹 Suggested fix-app.dependency_overrides[get_db] = override_get_db
-
`@pytest.fixture`(scope="module")
def client():
+ app.dependency_overrides[get_db] = override_get_db
# Create tables
Base.metadata.create_all(bind=test_engine)
@@
- # Cleanup
+ # Cleanup
+ app.dependency_overrides.pop(get_db, None)
Base.metadata.drop_all(bind=test_engine)
if os.path.exists(TEST_DB_FILE):
os.remove(TEST_DB_FILE)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def test_create_issues_and_blockchain_chaining(client): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 1. Create first issue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data1 = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "description": "First test issue for blockchain", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "category": "Road", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "latitude": 10.0, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "longitude": 20.0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| response1 = client.post("/api/issues", data=data1) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert response1.status_code == 201 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| issue1_id = response1.json()["id"] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Verify issue 1 has integrity hash and empty previous hash (or logic handling it) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| with TestingSessionLocal() as db: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| issue1 = db.query(Issue).filter(Issue.id == issue1_id).first() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert issue1.integrity_hash is not None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # First issue might have empty prev hash depending on DB state, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # but here DB is fresh so it should be empty string or similar. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expected_prev = "" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expected_prev = "" | |
| expected_prev = "" | |
| assert issue1.previous_integrity_hash == expected_prev |
Copilot
AI
Feb 26, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding a test for verifying the blockchain integrity of the first issue (issue1) to ensure the edge case of having an empty previous_integrity_hash is handled correctly by the verification endpoint. This would provide more comprehensive coverage of the blockchain verification logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assert issue creation before reading response payload.
If setup creation fails, this test currently cascades into unclear errors (id lookup on non-201 payload).
✅ Suggested fix
create_res = client.post("/api/issues", data=data)
+ assert create_res.status_code == 201
issue_id = create_res.json()["id"]🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/tests/test_issues_flow.py` around lines 149 - 151, Add an assertion
to verify the issue creation response succeeded before accessing its JSON
payload: check create_res.status_code (or use create_res.ok) and assert it
equals the expected success code (e.g., 201) with a helpful message, then only
after that line access create_res.json()["id"]; reference the test variable
create_res and the POST to "/api/issues" in backend/tests/test_issues_flow.py.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding a comment explaining the rationale for the limit of 100 records in the deduplication query. This helps future maintainers understand why this specific limit was chosen and prevents it from being accidentally removed or changed without consideration. For example: "Limit to 100 records to prevent performance bottlenecks in extremely dense areas while still ensuring adequate deduplication coverage within the 50-meter radius."