-
Notifications
You must be signed in to change notification settings - Fork 0
Jules was unable to complete the task in time. Please review the work… #67
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
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 | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -48,18 +48,32 @@ class SmartFilterManager: | |||||||||||||||
| def __init__(self, db_path: str = "smart_filters.db"): | ||||||||||||||||
| self.db_path = db_path | ||||||||||||||||
| self.logger = logging.getLogger(__name__) | ||||||||||||||||
| self.conn = None # For persistent in-memory connection | ||||||||||||||||
|
|
||||||||||||||||
| if self.db_path == ":memory:": | ||||||||||||||||
| self.conn = sqlite3.connect(":memory:") | ||||||||||||||||
| self.conn.row_factory = sqlite3.Row | ||||||||||||||||
|
Comment on lines
+53
to
+55
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. suggestion (bug_risk): Make in-memory SQLite connection thread-safe if shared across threads Consider setting check_same_thread=False when creating the connection, or use a separate connection for each thread to avoid threading errors.
Suggested change
|
||||||||||||||||
|
|
||||||||||||||||
| self._init_filter_db() | ||||||||||||||||
|
|
||||||||||||||||
| # Filter categories and templates | ||||||||||||||||
| self.filter_templates = self._load_filter_templates() | ||||||||||||||||
| self.pruning_criteria = self._load_pruning_criteria() | ||||||||||||||||
|
|
||||||||||||||||
| def _get_db_connection(self) -> sqlite3.Connection: | ||||||||||||||||
| """Get a new database connection.""" | ||||||||||||||||
| """Get a database connection.""" | ||||||||||||||||
| if self.conn: # Use persistent in-memory connection if it exists | ||||||||||||||||
| return self.conn | ||||||||||||||||
| # For file-based DBs, create a new connection each time (or manage a pool) | ||||||||||||||||
| conn = sqlite3.connect(self.db_path) | ||||||||||||||||
| conn.row_factory = sqlite3.Row # Access columns by name | ||||||||||||||||
| conn.row_factory = sqlite3.Row | ||||||||||||||||
| return conn | ||||||||||||||||
|
|
||||||||||||||||
| def _close_db_connection(self, conn: sqlite3.Connection): | ||||||||||||||||
| """Close connection if it's not the persistent in-memory one.""" | ||||||||||||||||
| if conn is not self.conn: | ||||||||||||||||
| conn.close() | ||||||||||||||||
|
|
||||||||||||||||
|
Comment on lines
+52
to
+76
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. 🛠️ Refactor suggestion Persistent in-memory connection logic is sound but leaks for file DBs
Consider:
Otherwise performance degradation is likely for heavy filter traffic. 🤖 Prompt for AI Agents |
||||||||||||||||
| def _db_execute(self, query: str, params: tuple = ()): | ||||||||||||||||
| """Execute a query (INSERT, UPDATE, DELETE).""" | ||||||||||||||||
| conn = self._get_db_connection() | ||||||||||||||||
|
|
@@ -70,7 +84,7 @@ def _db_execute(self, query: str, params: tuple = ()): | |||||||||||||||
| self.logger.error(f"Database error: {e} with query: {query[:100]}") | ||||||||||||||||
| # Optionally re-raise or handle | ||||||||||||||||
| finally: | ||||||||||||||||
| conn.close() | ||||||||||||||||
| self._close_db_connection(conn) | ||||||||||||||||
|
|
||||||||||||||||
| def _db_fetchone(self, query: str, params: tuple = ()) -> Optional[sqlite3.Row]: | ||||||||||||||||
| """Execute a query and fetch one row.""" | ||||||||||||||||
|
|
@@ -82,7 +96,7 @@ def _db_fetchone(self, query: str, params: tuple = ()) -> Optional[sqlite3.Row]: | |||||||||||||||
| self.logger.error(f"Database error: {e} with query: {query[:100]}") | ||||||||||||||||
| return None | ||||||||||||||||
| finally: | ||||||||||||||||
| conn.close() | ||||||||||||||||
| self._close_db_connection(conn) | ||||||||||||||||
|
|
||||||||||||||||
| def _db_fetchall(self, query: str, params: tuple = ()) -> List[sqlite3.Row]: | ||||||||||||||||
| """Execute a query and fetch all rows.""" | ||||||||||||||||
|
|
@@ -94,7 +108,7 @@ def _db_fetchall(self, query: str, params: tuple = ()) -> List[sqlite3.Row]: | |||||||||||||||
| self.logger.error(f"Database error: {e} with query: {query[:100]}") | ||||||||||||||||
| return [] | ||||||||||||||||
| finally: | ||||||||||||||||
| conn.close() | ||||||||||||||||
| self._close_db_connection(conn) | ||||||||||||||||
|
|
||||||||||||||||
| def _init_filter_db(self): | ||||||||||||||||
| """Initialize filter database""" | ||||||||||||||||
|
|
@@ -151,12 +165,20 @@ def _init_filter_db(self): | |||||||||||||||
| conn = self._get_db_connection() | ||||||||||||||||
| try: | ||||||||||||||||
| for query in queries: | ||||||||||||||||
| conn.execute(query) | ||||||||||||||||
| conn.commit() | ||||||||||||||||
| conn.execute(query) # Uses the connection from _get_db_connection | ||||||||||||||||
| conn.commit() # Commit on the same connection | ||||||||||||||||
| except sqlite3.Error as e: | ||||||||||||||||
| self.logger.error(f"Database initialization error: {e}") | ||||||||||||||||
| finally: | ||||||||||||||||
| conn.close() | ||||||||||||||||
| # Do not close if it's the persistent self.conn | ||||||||||||||||
| self._close_db_connection(conn) | ||||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
| def close(self): | ||||||||||||||||
| """Close the persistent database connection if it exists.""" | ||||||||||||||||
| if self.conn: | ||||||||||||||||
| self.conn.close() | ||||||||||||||||
| self.conn = None | ||||||||||||||||
|
|
||||||||||||||||
| def _load_filter_templates(self) -> Dict[str, Dict[str, Any]]: | ||||||||||||||||
| """Load intelligent filter templates""" | ||||||||||||||||
|
|
@@ -1059,7 +1081,8 @@ def _save_filter_performance(self, performance: FilterPerformance): | |||||||||||||||
| f1_score, processing_time_ms, emails_processed, true_positives, false_positives, false_negatives) | ||||||||||||||||
| VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) | ||||||||||||||||
| """ | ||||||||||||||||
| performance_id = f"{performance.filter_id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}" | ||||||||||||||||
| # Added microseconds for more unique ID | ||||||||||||||||
| performance_id = f"{performance.filter_id}_{datetime.now().strftime('%Y%m%d_%H%M%S_%f')}" | ||||||||||||||||
| params = ( | ||||||||||||||||
| performance_id, | ||||||||||||||||
| performance.filter_id, | ||||||||||||||||
|
|
||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,33 @@ | ||||||||||
| import pytest | ||||||||||
| from unittest.mock import AsyncMock | ||||||||||
| from server.python_backend.main import app # Assuming 'app' is your FastAPI instance | ||||||||||
| from server.python_backend.database import get_db # The actual dependency | ||||||||||
|
|
||||||||||
| @pytest.fixture(scope="session", autouse=True) | ||||||||||
| def mock_db_session_override(): | ||||||||||
| # Create a single AsyncMock instance for the session | ||||||||||
| mock_db = AsyncMock() | ||||||||||
| # Pre-configure any commonly returned objects or default behaviors if necessary | ||||||||||
| # For example, if many methods return a list: | ||||||||||
| # mock_db.get_all_categories = AsyncMock(return_value=[]) | ||||||||||
| # mock_db.get_emails = AsyncMock(return_value=[]) | ||||||||||
| # ... etc. Specific tests can override these. | ||||||||||
|
|
||||||||||
| # This is the dependency that needs to be overridden | ||||||||||
| # Ensure this path is correct for your project structure | ||||||||||
| original_get_db_override = app.dependency_overrides.get(get_db) # Store original override, if any | ||||||||||
|
|
||||||||||
| # Define the override function that will return our session-scoped mock | ||||||||||
| async def override_get_db_for_session(): | ||||||||||
| return mock_db | ||||||||||
|
|
||||||||||
|
Comment on lines
+21
to
+23
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. Dependency override should
• Fix: -async def override_get_db_for_session():
- return mock_db
+async def override_get_db_for_session():
+ yield mock_db📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| app.dependency_overrides[get_db] = override_get_db_for_session | ||||||||||
|
|
||||||||||
| yield mock_db # Provide the mock to tests if they request it by this fixture name | ||||||||||
|
|
||||||||||
| # Teardown: Restore original dependency override if it existed, or clear it | ||||||||||
| if original_get_db_override: | ||||||||||
| app.dependency_overrides[get_db] = original_get_db_override | ||||||||||
| else: | ||||||||||
| if get_db in app.dependency_overrides: # Check if key exists before deleting | ||||||||||
| del app.dependency_overrides[get_db] | ||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,11 @@ | ||
| import unittest | ||
| from unittest.mock import patch | ||
| # from server.python_nlp.action_item_extractor import ActionItemExtractor, HAS_NLTK # Commented out for debug | ||
| HAS_NLTK = False # Stubbing HAS_NLTK | ||
| from server.python_nlp.action_item_extractor import ActionItemExtractor, HAS_NLTK | ||
|
|
||
| class TestActionItemExtractor(unittest.TestCase): | ||
|
|
||
| def setUp(self): | ||
| # self.extractor = ActionItemExtractor() # Commented out for debug | ||
| self.extractor = None # Placeholder | ||
| self.extractor = ActionItemExtractor() | ||
|
|
||
| def test_extract_actions_clear_phrase_with_due_date(self): | ||
| text = "Please review the attached document by Friday." | ||
|
|
@@ -76,15 +74,16 @@ def test_extract_actions_multiple_action_items(self): | |
|
|
||
|
|
||
| def test_extract_actions_simple_due_date_tomorrow(self): | ||
| text = "Submit the expenses by tomorrow." | ||
| actions = self.extractor.extract_actions(text) | ||
| self.assertEqual(len(actions), 1) # Assuming "Submit" isn't a keyword, but "by tomorrow" is part of sentence | ||
| # The current extractor triggers on keywords like "please", "need to" etc. | ||
| # Let's adjust the text to include a keyword. | ||
| text_no_keyword = "Submit the expenses by tomorrow." | ||
| actions_no_keyword = self.extractor.extract_actions(text_no_keyword) | ||
| self.assertEqual(len(actions_no_keyword), 0) # Text without keyword should not produce action | ||
|
|
||
| # Now test with a keyword | ||
| text_with_keyword = "You should Submit the expenses by tomorrow." | ||
| actions = self.extractor.extract_actions(text_with_keyword) | ||
| self.assertEqual(len(actions), 1) | ||
| self.assertEqual(actions[0]['raw_due_date_text'], "by tomorrow") | ||
| actions_with_keyword = self.extractor.extract_actions(text_with_keyword) | ||
| self.assertEqual(len(actions_with_keyword), 1) | ||
|
Comment on lines
+77
to
+84
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. issue (code-quality): Extract duplicate code into method ( |
||
| self.assertEqual(actions_with_keyword[0]['raw_due_date_text'], "by tomorrow") | ||
| self.assertTrue(actions_with_keyword[0]['action_phrase'].startswith("should Submit the expenses")) | ||
|
|
||
| def test_extract_actions_due_date_on_monday(self): | ||
| text = "We need to finish this on Monday." | ||
|
|
||
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.
Awaiting a non-coroutine will raise a runtime
TypeError.SmartFilterManager.apply_filters_to_email_datais a synchronous method (seesmart_filters.py).awaitexpects a coroutine / awaitable; in production this line will blow up once the patch-based tests are removed.Alternatively, convert
apply_filters_to_email_datatoasync defif you truly need asynchronous behaviour.📝 Committable suggestion
🤖 Prompt for AI Agents