from __future__ import annotations from django.db import transaction from apps.jobs.services.geocoding import LocationMatchResult, resolve_cached_location from .models import ProfileRevision, SearchProfile @transaction.atomic def resolve_profile_home_location(profile: SearchProfile) -> LocationMatchResult: """Resolve a user-entered postal code without exposing coordinate fields in the UI.""" query = profile.home_postal_code.strip() result = ( resolve_cached_location(query) if query else LocationMatchResult(query=query, location=None) ) location = result.location profile.home_municipality = location.municipality or "" if location else "" profile.home_latitude = location.point.latitude if location and location.point else None profile.home_longitude = location.point.longitude if location and location.point else None profile.save( update_fields=[ "home_municipality", "home_latitude", "home_longitude", "updated_at", ] ) return result @transaction.atomic def save_profile_revision(profile: SearchProfile, *, reason: str) -> ProfileRevision: latest = profile.revisions.order_by("-version").first() next_version = (latest.version if latest else 0) + 1 if profile.version != next_version: profile.version = next_version profile.save(update_fields=["version", "updated_at"]) return ProfileRevision.objects.create( profile=profile, version=next_version, snapshot=profile.snapshot(), reason=reason, ) @transaction.atomic def apply_feedback_delta( profile: SearchProfile, feature: str, delta: float, *, min_weight: float = 0.0, max_weight: float = 40.0, ) -> SearchProfile: if not profile.learning_enabled: return profile weights = dict(profile.weights) current = float(weights.get(feature, 0.0)) weights[feature] = round(max(min_weight, min(max_weight, current + delta)), 2) profile.weights = weights profile.save(update_fields=["weights", "updated_at"]) save_profile_revision(profile, reason=f"feedback_delta:{feature}:{delta:+.2f}") return profile