prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Add verifier property to Events
  Soon users will have multiple pathways to verify eligibility, and it would be useful to know how many users are taking which pathway(s) in our analytics. We can get ahead of this now by adding an event property with the user's selected verifier (if any).
  
  Let's add this to the base Event definition so that we can later filter any event type by this property, similar to the Transit Agency information attached to each event.
  
  Linking this Amplitude reference again: https://help.amplitude.com/hc/en-us/articles/115002380567-User-properties-and-event-properties
  
  ## Acceptance Criteria
  
  <!-- Remember to consider edge cases -->
  
  - [ ] The base Event has an `eligibility_verifier` event property that gets its value from the user's `session.verifier()`
  - [ ] The Amplitude Tracking Plan is updated to accept this event property
  - [ ] The Amplitude --> Warehouse --> Metabase pipeline is updated to ingest this property
  
  ## Additional context
  
  This task was broken out from #342 and is focused on adding the verifier information to _existing_ events. #342 is now focused on introducing _new_ events for some forthcoming UI.
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `benefits/core/analytics.py`
  Content:
  ```
  1 """
  2 The core application: analytics implementation.
  3 """
  4 import itertools
  5 import json
  6 import logging
  7 import re
  8 import time
  9 import uuid
  10 
  11 from django.conf import settings
  12 import requests
  13 
  14 from benefits import VERSION
  15 from . import session
  16 
  17 
  18 logger = logging.getLogger(__name__)
  19 
  20 
  21 class Event:
  22     """Base analytics event of a given type, including attributes from request's session."""
  23 
  24     _counter = itertools.count()
  25     _domain_re = re.compile(r"^(?:https?:\/\/)?(?:[^@\n]+@)?(?:www\.)?([^:\/\n?]+)", re.IGNORECASE)
  26 
  27     def __init__(self, request, event_type, **kwargs):
  28         self.app_version = VERSION
  29         # device_id is generated based on the user_id, and both are set explicitly (per session)
  30         self.device_id = session.did(request)
  31         self.event_properties = {}
  32         self.event_type = str(event_type).lower()
  33         self.insert_id = str(uuid.uuid4())
  34         self.language = session.language(request)
  35         # Amplitude tracks sessions using the start time as the session_id
  36         self.session_id = session.start(request)
  37         self.time = int(time.time() * 1000)
  38         # Although Amplitude advises *against* setting user_id for anonymous users, here a value is set on anonymous
  39         # users anyway, as the users never sign-in and become de-anonymized to this app / Amplitude.
  40         self.user_id = session.uid(request)
  41         self.user_properties = {}
  42         self.__dict__.update(kwargs)
  43 
  44         agency = session.agency(request)
  45         agency_name = agency.long_name if agency else None
  46 
  47         self.update_event_properties(path=request.path, transit_agency=agency_name)
  48 
  49         uagent = request.headers.get("user-agent")
  50 
  51         ref = request.headers.get("referer")
  52         match = Event._domain_re.match(ref) if ref else None
  53         refdom = match.group(1) if match else None
  54 
  55         self.update_user_properties(referrer=ref, referring_domain=refdom, user_agent=uagent, transit_agency=agency_name)
  56 
  57         # event is initialized, consume next counter
  58         self.event_id = next(Event._counter)
  59 
  60     def __str__(self):
  61         return json.dumps(self.__dict__)
  62 
  63     def update_event_properties(self, **kwargs):
  64         """Merge kwargs into the self.event_properties dict."""
  65         self.event_properties.update(kwargs)
  66 
  67     def update_user_properties(self, **kwargs):
  68         """Merge kwargs into the self.user_properties dict."""
  69         self.user_properties.update(kwargs)
  70 
  71 
  72 class ViewedPageEvent(Event):
  73     """Analytics event representing a single page view."""
  74 
  75     def __init__(self, request):
  76         super().__init__(request, "viewed page")
  77 
  78 
  79 class ChangedLanguageEvent(Event):
  80     """Analytics event representing a change in the app's language."""
  81 
  82     def __init__(self, request, new_lang):
  83         super().__init__(request, "changed language")
  84         self.update_event_properties(language=new_lang)
  85 
  86 
  87 class Client:
  88     """Analytics API client"""
  89 
  90     def __init__(self, api_key):
  91         self.api_key = api_key
  92         self.headers = {"Accept": "*/*", "Content-type": "application/json"}
  93         self.url = "https://api2.amplitude.com/2/httpapi"
  94         logger.debug(f"Initialize Client for {self.url}")
  95 
  96     def _payload(self, events):
  97         if not isinstance(events, list):
  98             events = [events]
  99         return {"api_key": self.api_key, "events": [e.__dict__ for e in events]}
  100 
  101     def send(self, event):
  102         """Send an analytics event."""
  103         if not isinstance(event, Event):
  104             raise ValueError("event must be an Event instance")
  105 
  106         if not self.api_key:
  107             logger.warning(f"api_key is not configured, cannot send event: {event}")
  108             return
  109 
  110         try:
  111             payload = self._payload(event)
  112             logger.debug(f"Sending event payload: {payload}")
  113 
  114             r = requests.post(self.url, headers=self.headers, json=payload)
  115             if r.status_code == 200:
  116                 logger.debug(f"Event sent successfully: {r.json()}")
  117             elif r.status_code == 400:
  118                 logger.error(f"Event request was invalid: {r.json()}")
  119             elif r.status_code == 413:
  120                 logger.error(f"Event payload was too large: {r.json()}")
  121             elif r.status_code == 429:
  122                 logger.error(f"Event contained too many requests for some users: {r.json()}")
  123             else:
  124                 logger.error(f"Failed to send event: {r.json()}")
  125 
  126         except Exception:
  127             logger.error(f"Failed to send event: {event}")
  128 
  129 
  130 client = Client(settings.ANALYTICS_KEY)
  131 
  132 
  133 def send_event(event):
  134     """Send an analytics event."""
  135     if isinstance(event, Event):
  136         client.send(event)
  137     else:
  138         raise ValueError("event must be an Event instance")
  139
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/benefits/core/analytics.py b/benefits/core/analytics.py
  --- a/benefits/core/analytics.py
  +++ b/benefits/core/analytics.py
  @@ -43,8 +43,10 @@
   
           agency = session.agency(request)
           agency_name = agency.long_name if agency else None
  +        verifier = session.verifier(request)
  +        verifier_name = verifier.name if verifier else None
   
  -        self.update_event_properties(path=request.path, transit_agency=agency_name)
  +        self.update_event_properties(path=request.path, transit_agency=agency_name, eligibility_verifier=verifier_name)
   
           uagent = request.headers.get("user-agent")
   
  @@ -52,7 +54,13 @@
           match = Event._domain_re.match(ref) if ref else None
           refdom = match.group(1) if match else None
   
  -        self.update_user_properties(referrer=ref, referring_domain=refdom, user_agent=uagent, transit_agency=agency_name)
  +        self.update_user_properties(
  +            referrer=ref,
  +            referring_domain=refdom,
  +            user_agent=uagent,
  +            transit_agency=agency_name,
  +            eligibility_verifier=verifier_name,
  +        )
   
           # event is initialized, consume next counter
           self.event_id = next(Event._counter)
  
verification_info: {"golden_diff": "diff --git a/benefits/core/analytics.py b/benefits/core/analytics.py\n--- a/benefits/core/analytics.py\n+++ b/benefits/core/analytics.py\n@@ -43,8 +43,10 @@\n \n         agency = session.agency(request)\n         agency_name = agency.long_name if agency else None\n+        verifier = session.verifier(request)\n+        verifier_name = verifier.name if verifier else None\n \n-        self.update_event_properties(path=request.path, transit_agency=agency_name)\n+        self.update_event_properties(path=request.path, transit_agency=agency_name, eligibility_verifier=verifier_name)\n \n         uagent = request.headers.get(\"user-agent\")\n \n@@ -52,7 +54,13 @@\n         match = Event._domain_re.match(ref) if ref else None\n         refdom = match.group(1) if match else None\n \n-        self.update_user_properties(referrer=ref, referring_domain=refdom, user_agent=uagent, transit_agency=agency_name)\n+        self.update_user_properties(\n+            referrer=ref,\n+            referring_domain=refdom,\n+            user_agent=uagent,\n+            transit_agency=agency_name,\n+            eligibility_verifier=verifier_name,\n+        )\n \n         # event is initialized, consume next counter\n         self.event_id = next(Event._counter)\n", "issue": "Add verifier property to Events\nSoon users will have multiple pathways to verify eligibility, and it would be useful to know how many users are taking which pathway(s) in our analytics. We can get ahead of this now by adding an event property with the user's selected verifier (if any).\r\n\r\nLet's add this to the base Event definition so that we can later filter any event type by this property, similar to the Transit Agency information attached to each event.\r\n\r\nLinking this Amplitude reference again: https://help.amplitude.com/hc/en-us/articles/115002380567-User-properties-and-event-properties\r\n\r\n## Acceptance Criteria\r\n\r\n<!-- Remember to consider edge cases -->\r\n\r\n- [ ] The base Event has an `eligibility_verifier` event property that gets its value from the user's `session.verifier()`\r\n- [ ] The Amplitude Tracking Plan is updated to accept this event property\r\n- [ ] The Amplitude --> Warehouse --> Metabase pipeline is updated to ingest this property\r\n\r\n## Additional context\r\n\r\nThis task was broken out from #342 and is focused on adding the verifier information to _existing_ events. #342 is now focused on introducing _new_ events for some forthcoming UI.\r\n\n", "before_files": [{"content": "\"\"\"\nThe core application: analytics implementation.\n\"\"\"\nimport itertools\nimport json\nimport logging\nimport re\nimport time\nimport uuid\n\nfrom django.conf import settings\nimport requests\n\nfrom benefits import VERSION\nfrom . import session\n\n\nlogger = logging.getLogger(__name__)\n\n\nclass Event:\n    \"\"\"Base analytics event of a given type, including attributes from request's session.\"\"\"\n\n    _counter = itertools.count()\n    _domain_re = re.compile(r\"^(?:https?:\\/\\/)?(?:[^@\\n]+@)?(?:www\\.)?([^:\\/\\n?]+)\", re.IGNORECASE)\n\n    def __init__(self, request, event_type, **kwargs):\n        self.app_version = VERSION\n        # device_id is generated based on the user_id, and both are set explicitly (per session)\n        self.device_id = session.did(request)\n        self.event_properties = {}\n        self.event_type = str(event_type).lower()\n        self.insert_id = str(uuid.uuid4())\n        self.language = session.language(request)\n        # Amplitude tracks sessions using the start time as the session_id\n        self.session_id = session.start(request)\n        self.time = int(time.time() * 1000)\n        # Although Amplitude advises *against* setting user_id for anonymous users, here a value is set on anonymous\n        # users anyway, as the users never sign-in and become de-anonymized to this app / Amplitude.\n        self.user_id = session.uid(request)\n        self.user_properties = {}\n        self.__dict__.update(kwargs)\n\n        agency = session.agency(request)\n        agency_name = agency.long_name if agency else None\n\n        self.update_event_properties(path=request.path, transit_agency=agency_name)\n\n        uagent = request.headers.get(\"user-agent\")\n\n        ref = request.headers.get(\"referer\")\n        match = Event._domain_re.match(ref) if ref else None\n        refdom = match.group(1) if match else None\n\n        self.update_user_properties(referrer=ref, referring_domain=refdom, user_agent=uagent, transit_agency=agency_name)\n\n        # event is initialized, consume next counter\n        self.event_id = next(Event._counter)\n\n    def __str__(self):\n        return json.dumps(self.__dict__)\n\n    def update_event_properties(self, **kwargs):\n        \"\"\"Merge kwargs into the self.event_properties dict.\"\"\"\n        self.event_properties.update(kwargs)\n\n    def update_user_properties(self, **kwargs):\n        \"\"\"Merge kwargs into the self.user_properties dict.\"\"\"\n        self.user_properties.update(kwargs)\n\n\nclass ViewedPageEvent(Event):\n    \"\"\"Analytics event representing a single page view.\"\"\"\n\n    def __init__(self, request):\n        super().__init__(request, \"viewed page\")\n\n\nclass ChangedLanguageEvent(Event):\n    \"\"\"Analytics event representing a change in the app's language.\"\"\"\n\n    def __init__(self, request, new_lang):\n        super().__init__(request, \"changed language\")\n        self.update_event_properties(language=new_lang)\n\n\nclass Client:\n    \"\"\"Analytics API client\"\"\"\n\n    def __init__(self, api_key):\n        self.api_key = api_key\n        self.headers = {\"Accept\": \"*/*\", \"Content-type\": \"application/json\"}\n        self.url = \"https://api2.amplitude.com/2/httpapi\"\n        logger.debug(f\"Initialize Client for {self.url}\")\n\n    def _payload(self, events):\n        if not isinstance(events, list):\n            events = [events]\n        return {\"api_key\": self.api_key, \"events\": [e.__dict__ for e in events]}\n\n    def send(self, event):\n        \"\"\"Send an analytics event.\"\"\"\n        if not isinstance(event, Event):\n            raise ValueError(\"event must be an Event instance\")\n\n        if not self.api_key:\n            logger.warning(f\"api_key is not configured, cannot send event: {event}\")\n            return\n\n        try:\n            payload = self._payload(event)\n            logger.debug(f\"Sending event payload: {payload}\")\n\n            r = requests.post(self.url, headers=self.headers, json=payload)\n            if r.status_code == 200:\n                logger.debug(f\"Event sent successfully: {r.json()}\")\n            elif r.status_code == 400:\n                logger.error(f\"Event request was invalid: {r.json()}\")\n            elif r.status_code == 413:\n                logger.error(f\"Event payload was too large: {r.json()}\")\n            elif r.status_code == 429:\n                logger.error(f\"Event contained too many requests for some users: {r.json()}\")\n            else:\n                logger.error(f\"Failed to send event: {r.json()}\")\n\n        except Exception:\n            logger.error(f\"Failed to send event: {event}\")\n\n\nclient = Client(settings.ANALYTICS_KEY)\n\n\ndef send_event(event):\n    \"\"\"Send an analytics event.\"\"\"\n    if isinstance(event, Event):\n        client.send(event)\n    else:\n        raise ValueError(\"event must be an Event instance\")\n", "path": "benefits/core/analytics.py"}], "after_files": [{"content": "\"\"\"\nThe core application: analytics implementation.\n\"\"\"\nimport itertools\nimport json\nimport logging\nimport re\nimport time\nimport uuid\n\nfrom django.conf import settings\nimport requests\n\nfrom benefits import VERSION\nfrom . import session\n\n\nlogger = logging.getLogger(__name__)\n\n\nclass Event:\n    \"\"\"Base analytics event of a given type, including attributes from request's session.\"\"\"\n\n    _counter = itertools.count()\n    _domain_re = re.compile(r\"^(?:https?:\\/\\/)?(?:[^@\\n]+@)?(?:www\\.)?([^:\\/\\n?]+)\", re.IGNORECASE)\n\n    def __init__(self, request, event_type, **kwargs):\n        self.app_version = VERSION\n        # device_id is generated based on the user_id, and both are set explicitly (per session)\n        self.device_id = session.did(request)\n        self.event_properties = {}\n        self.event_type = str(event_type).lower()\n        self.insert_id = str(uuid.uuid4())\n        self.language = session.language(request)\n        # Amplitude tracks sessions using the start time as the session_id\n        self.session_id = session.start(request)\n        self.time = int(time.time() * 1000)\n        # Although Amplitude advises *against* setting user_id for anonymous users, here a value is set on anonymous\n        # users anyway, as the users never sign-in and become de-anonymized to this app / Amplitude.\n        self.user_id = session.uid(request)\n        self.user_properties = {}\n        self.__dict__.update(kwargs)\n\n        agency = session.agency(request)\n        agency_name = agency.long_name if agency else None\n        verifier = session.verifier(request)\n        verifier_name = verifier.name if verifier else None\n\n        self.update_event_properties(path=request.path, transit_agency=agency_name, eligibility_verifier=verifier_name)\n\n        uagent = request.headers.get(\"user-agent\")\n\n        ref = request.headers.get(\"referer\")\n        match = Event._domain_re.match(ref) if ref else None\n        refdom = match.group(1) if match else None\n\n        self.update_user_properties(\n            referrer=ref,\n            referring_domain=refdom,\n            user_agent=uagent,\n            transit_agency=agency_name,\n            eligibility_verifier=verifier_name,\n        )\n\n        # event is initialized, consume next counter\n        self.event_id = next(Event._counter)\n\n    def __str__(self):\n        return json.dumps(self.__dict__)\n\n    def update_event_properties(self, **kwargs):\n        \"\"\"Merge kwargs into the self.event_properties dict.\"\"\"\n        self.event_properties.update(kwargs)\n\n    def update_user_properties(self, **kwargs):\n        \"\"\"Merge kwargs into the self.user_properties dict.\"\"\"\n        self.user_properties.update(kwargs)\n\n\nclass ViewedPageEvent(Event):\n    \"\"\"Analytics event representing a single page view.\"\"\"\n\n    def __init__(self, request):\n        super().__init__(request, \"viewed page\")\n\n\nclass ChangedLanguageEvent(Event):\n    \"\"\"Analytics event representing a change in the app's language.\"\"\"\n\n    def __init__(self, request, new_lang):\n        super().__init__(request, \"changed language\")\n        self.update_event_properties(language=new_lang)\n\n\nclass Client:\n    \"\"\"Analytics API client\"\"\"\n\n    def __init__(self, api_key):\n        self.api_key = api_key\n        self.headers = {\"Accept\": \"*/*\", \"Content-type\": \"application/json\"}\n        self.url = \"https://api2.amplitude.com/2/httpapi\"\n        logger.debug(f\"Initialize Client for {self.url}\")\n\n    def _payload(self, events):\n        if not isinstance(events, list):\n            events = [events]\n        return {\"api_key\": self.api_key, \"events\": [e.__dict__ for e in events]}\n\n    def send(self, event):\n        \"\"\"Send an analytics event.\"\"\"\n        if not isinstance(event, Event):\n            raise ValueError(\"event must be an Event instance\")\n\n        if not self.api_key:\n            logger.warning(f\"api_key is not configured, cannot send event: {event}\")\n            return\n\n        try:\n            payload = self._payload(event)\n            logger.debug(f\"Sending event payload: {payload}\")\n\n            r = requests.post(self.url, headers=self.headers, json=payload)\n            if r.status_code == 200:\n                logger.debug(f\"Event sent successfully: {r.json()}\")\n            elif r.status_code == 400:\n                logger.error(f\"Event request was invalid: {r.json()}\")\n            elif r.status_code == 413:\n                logger.error(f\"Event payload was too large: {r.json()}\")\n            elif r.status_code == 429:\n                logger.error(f\"Event contained too many requests for some users: {r.json()}\")\n            else:\n                logger.error(f\"Failed to send event: {r.json()}\")\n\n        except Exception:\n            logger.error(f\"Failed to send event: {event}\")\n\n\nclient = Client(settings.ANALYTICS_KEY)\n\n\ndef send_event(event):\n    \"\"\"Send an analytics event.\"\"\"\n    if isinstance(event, Event):\n        client.send(event)\n    else:\n        raise ValueError(\"event must be an Event instance\")\n", "path": "benefits/core/analytics.py"}]}