-
Notifications
You must be signed in to change notification settings - Fork 1
Geocoding Cron Job #76
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
Merged
tqmsh
merged 5 commits into
uwblueprint:TQ/geocoding-refresh-cron-job
from
Yujie-Meng:main
Jan 12, 2026
Merged
Changes from 2 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
1eacbfa
Add geocoding refresh cron job and related fields
Yujie-Meng 6b4eb80
Update README to include Geocoding Update section and add screenshots
Yujie-Meng 12dad9a
Resolved commits on PR
Yujie-Meng ab2f748
removed appropriate migrations.
Yujie-Meng c57dd10
Refactor geocoding refresh logic into helper functions
Yujie-Meng File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
118 changes: 118 additions & 0 deletions
118
backend/python/app/services/jobs/geocoding_refresh_jobs.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| """Geocoding refresh scheduled jobs""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from datetime import datetime, timedelta | ||
| from typing import TYPE_CHECKING | ||
|
|
||
| if TYPE_CHECKING: | ||
| from uuid import UUID | ||
|
|
||
| from sqlalchemy import and_, or_ | ||
| from sqlmodel import select | ||
|
|
||
| from app.dependencies.services import get_logger | ||
| from app.models import async_session_maker_instance | ||
| from app.models.admin import Admin | ||
| from app.models.location import Location | ||
| from app.utilities.geocoding import geocode, geocode_addresses | ||
|
|
||
|
|
||
| async def refresh_geocoding() -> None: | ||
Yujie-Meng marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| """Refresh geocoding for locations and warehouse - runs daily | ||
|
|
||
| This job: | ||
| 1. Gets the route_archive_after setting from admin_info (default 30 days) | ||
| 2. Finds all locations that need refreshing (geocoded_at is null or older than threshold) | ||
| 3. Refreshes lat/lon for those locations using Google Geocoding API | ||
| 4. Updates geocoded_at timestamp for refreshed locations | ||
| 5. Refreshes the warehouse location in admin_info table | ||
| """ | ||
| logger = get_logger() | ||
|
|
||
| if async_session_maker_instance is None: | ||
| logger.error("Database session maker not initialized") | ||
| return | ||
|
|
||
| try: | ||
| async with async_session_maker_instance() as session: | ||
| # Get the admin settings to determine archive threshold | ||
| admin_statement = select(Admin) | ||
| admin_result = await session.execute(admin_statement) | ||
| admin_record = admin_result.scalars().first() | ||
|
|
||
| if not admin_record: | ||
| logger.warning("No admin record found, using default threshold of 30 days") | ||
| archive_threshold_days = 30 | ||
| else: | ||
| archive_threshold_days = admin_record.route_archive_after or 30 | ||
| logger.info(f"Using route_archive_after threshold: {archive_threshold_days} days") | ||
|
|
||
| # Calculate the cutoff date | ||
| cutoff_date = datetime.now() - timedelta(days=archive_threshold_days) | ||
|
|
||
| # Find all locations that need refreshing | ||
| # (geocoded_at is null OR geocoded_at < cutoff_date) | ||
| location_statement = select(Location).where( | ||
| or_( | ||
| Location.geocoded_at.is_(None), | ||
| Location.geocoded_at < cutoff_date | ||
| ) | ||
| ) | ||
| location_result = await session.execute(location_statement) | ||
| locations_to_refresh = location_result.scalars().all() | ||
|
|
||
| if not locations_to_refresh: | ||
| logger.info("No locations need geocoding refresh") | ||
| else: | ||
| logger.info(f"Found {len(locations_to_refresh)} locations to refresh") | ||
|
|
||
| # Collect addresses and their corresponding location IDs | ||
| addresses = [loc.address for loc in locations_to_refresh] | ||
|
|
||
| # Batch geocode all addresses | ||
| geocoding_results = await geocode_addresses(addresses) | ||
|
|
||
| # Update each location with new coordinates | ||
| refreshed_count = 0 | ||
| for location, coords in zip(locations_to_refresh, geocoding_results): | ||
| if coords: | ||
| location.latitude = coords["lat"] | ||
| location.longitude = coords["lng"] | ||
| location.geocoded_at = datetime.now() | ||
| refreshed_count += 1 | ||
| logger.info( | ||
| f"Refreshed geocoding for location {location.location_id} " | ||
| f"({location.address}): {coords}" | ||
| ) | ||
| else: | ||
| logger.warning( | ||
| f"Failed to geocode location {location.location_id} " | ||
| f"({location.address})" | ||
| ) | ||
|
|
||
| await session.commit() | ||
| logger.info(f"Successfully refreshed {refreshed_count}/{len(locations_to_refresh)} locations") | ||
|
|
||
| # Refresh warehouse location if it exists | ||
| if admin_record and admin_record.warehouse_location: | ||
| logger.info("Refreshing warehouse location geocoding") | ||
| warehouse_coords = await geocode(admin_record.warehouse_location) | ||
|
|
||
| if warehouse_coords: | ||
| # Store warehouse coordinates in a separate table or add fields to admin | ||
| # For now, we'll just log it since admin table doesn't have lat/lng fields | ||
Yujie-Meng marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| logger.info( | ||
| f"Warehouse location geocoded: {admin_record.warehouse_location} -> {warehouse_coords}" | ||
| ) | ||
| # TODO: Add warehouse_latitude and warehouse_longitude fields to admin table if needed | ||
| else: | ||
| logger.warning( | ||
| f"Failed to geocode warehouse location: {admin_record.warehouse_location}" | ||
| ) | ||
|
|
||
| except Exception as error: | ||
| logger.error( | ||
| f"Failed to refresh geocoding: {error!s}", exc_info=True | ||
| ) | ||
| raise error | ||
27 changes: 27 additions & 0 deletions
27
backend/python/migrations/versions/6dc0749d4ffd_add_geocoding_refresh_fields.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| """Add geocoding refresh fields | ||
Yujie-Meng marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| Revision ID: 6dc0749d4ffd | ||
| Revises: 6e14b59510ce4d28a7af0b0f3f4d3385 | ||
| Create Date: 2025-11-28 21:00:00.000000 | ||
|
|
||
| """ | ||
| from alembic import op | ||
| import sqlalchemy as sa | ||
| from sqlalchemy.dialects import postgresql | ||
|
|
||
| # revision identifiers, used by Alembic. | ||
| revision = '6dc0749d4ffd' | ||
| down_revision = '6e14b59510ce4d28a7af0b0f3f4d3385' | ||
| branch_labels = None | ||
| depends_on = None | ||
|
|
||
|
|
||
| def upgrade() -> None: | ||
| op.add_column('locations', sa.Column('geocoded_at', sa.DateTime(), nullable=True)) | ||
| op.add_column('admin_info', sa.Column('route_archive_after', sa.Integer(), nullable=False, server_default='30')) | ||
| op.alter_column('admin_info', 'route_archive_after', nullable=False) | ||
|
|
||
|
|
||
| def downgrade() -> None: | ||
| op.drop_column('admin_info', 'route_archive_after') | ||
| op.drop_column('locations', 'geocoded_at') | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.