-
-
Notifications
You must be signed in to change notification settings - Fork 294
Added AI processing to events #1066
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 7 commits
0e909cd
6627f6d
6b528a6
a399d50
93a577d
046c059
a14d0a3
05f9c57
4eb7b29
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 |
|---|---|---|
|
|
@@ -16,5 +16,7 @@ class Meta: | |
| "key", | ||
| "name", | ||
| "start_date", | ||
| "suggested_location", | ||
| "summary", | ||
| "url", | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| """A command to enrich events with extra data.""" | ||
|
|
||
| import logging | ||
| import time | ||
|
|
||
| from django.core.management.base import BaseCommand | ||
|
|
||
| from apps.core.models.prompt import Prompt | ||
| from apps.owasp.models.event import Event | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| class Command(BaseCommand): | ||
| help = "Enrich events with extra data." | ||
|
|
||
| def add_arguments(self, parser): | ||
| parser.add_argument("--offset", default=0, required=False, type=int) | ||
|
|
||
| def handle(self, *args, **options): | ||
| events = Event.objects.order_by("id") | ||
| all_events = [] | ||
| offset = options["offset"] | ||
|
|
||
| for idx, event in enumerate(events[offset:]): | ||
| prefix = f"{idx + offset + 1} of {events.count()}" | ||
| print(f"{prefix:<10} {event.url}") | ||
| # Summary. | ||
| if not event.summary and (prompt := Prompt.get_owasp_event_summary()): | ||
| event.generate_summary(prompt=prompt) | ||
|
|
||
| # Suggested location. | ||
| if not event.suggested_location and ( | ||
| prompt := Prompt.get_owasp_event_suggested_location() | ||
| ): | ||
| event.generate_suggested_location() | ||
|
|
||
| # Geo location. | ||
| if not event.latitude or not event.longitude: | ||
| try: | ||
| event.generate_geo_location() | ||
| time.sleep(5) | ||
| except Exception: | ||
| logger.exception( | ||
| "Could not get geo data for event", | ||
| extra={"url": event.url}, | ||
| ) | ||
| all_events.append(event) | ||
|
|
||
| Event.bulk_save( | ||
| all_events, | ||
| fields=("latitude", "longitude", "suggested_location", "summary"), | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| # Generated by Django 5.1.7 on 2025-03-08 20:29 | ||
|
|
||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
| dependencies = [ | ||
| ("owasp", "0022_sponsor"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.AddField( | ||
| model_name="event", | ||
| name="latitude", | ||
| field=models.FloatField(blank=True, null=True, verbose_name="Latitude"), | ||
| ), | ||
| migrations.AddField( | ||
| model_name="event", | ||
| name="longitude", | ||
| field=models.FloatField(blank=True, null=True, verbose_name="Longitude"), | ||
| ), | ||
| migrations.AddField( | ||
| model_name="event", | ||
| name="suggested_location", | ||
| field=models.CharField( | ||
| blank=True, default="", max_length=255, verbose_name="Suggested Location" | ||
| ), | ||
| ), | ||
| migrations.AddField( | ||
| model_name="event", | ||
| name="summary", | ||
| field=models.TextField(blank=True, default="", verbose_name="Summary"), | ||
| ), | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| # Generated by Django 5.1.7 on 2025-03-09 09:12 | ||
|
|
||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
| dependencies = [ | ||
| ("owasp", "0023_event_latitude_event_longitude_and_more"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.AddField( | ||
| model_name="event", | ||
| name="country", | ||
| field=models.CharField(default="", max_length=50, verbose_name="Country"), | ||
| ), | ||
| migrations.AddField( | ||
| model_name="event", | ||
| name="postal_code", | ||
| field=models.CharField( | ||
| blank=True, default="", max_length=15, verbose_name="Postal code" | ||
| ), | ||
| ), | ||
| migrations.AddField( | ||
| model_name="event", | ||
| name="region", | ||
| field=models.CharField(default="", max_length=50, verbose_name="Region"), | ||
| ), | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| # Generated by Django 5.1.7 on 2025-03-10 02:28 | ||
|
|
||
| from django.db import migrations | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
| dependencies = [ | ||
| ("owasp", "0024_event_country_event_postal_code_event_region"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.RemoveField( | ||
| model_name="event", | ||
| name="country", | ||
| ), | ||
| migrations.RemoveField( | ||
| model_name="event", | ||
| name="postal_code", | ||
| ), | ||
| migrations.RemoveField( | ||
| model_name="event", | ||
| name="region", | ||
| ), | ||
| ] |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -4,8 +4,12 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from django.db import models | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from django.utils import timezone | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from apps.common.constants import NL | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from apps.common.geocoding import get_location_coordinates | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from apps.common.models import BulkSaveModel, TimestampedModel | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from apps.common.utils import slugify | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from apps.common.open_ai import OpenAi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from apps.common.utils import join_values, slugify | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from apps.core.models.prompt import Prompt | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from apps.github.utils import normalize_url | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -30,13 +34,18 @@ class Category(models.TextChoices): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| choices=Category.choices, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| default=Category.OTHER, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end_date = models.DateField(verbose_name="End Date", null=True, blank=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key = models.CharField(verbose_name="Key", max_length=100, unique=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name = models.CharField(verbose_name="Name", max_length=100) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description = models.TextField(verbose_name="Description", default="", blank=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| start_date = models.DateField(verbose_name="Start Date") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end_date = models.DateField(verbose_name="End Date", null=True, blank=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description = models.TextField(verbose_name="Description", default="", blank=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key = models.CharField(verbose_name="Key", max_length=100, unique=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| summary = models.TextField(verbose_name="Summary", blank=True, default="") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| suggested_location = models.CharField( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| verbose_name="Suggested Location", max_length=255, blank=True, default="" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url = models.URLField(verbose_name="URL", default="", blank=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| latitude = models.FloatField(verbose_name="Latitude", null=True, blank=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| longitude = models.FloatField(verbose_name="Longitude", null=True, blank=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def __str__(self): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Event human readable representation.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -130,3 +139,65 @@ def from_dict(self, category, data): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for key, value in fields.items(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setattr(self, key, value) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def generate_summary(self, prompt): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Generate a summary for the event using OpenAI.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not prompt: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| open_ai = OpenAi() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not self.description: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| open_ai.set_input( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| join_values( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| f"Name: {self.name}", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| f"Description: {self.description}", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| f"Dates: {self.start_date} - {self.end_date}", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| delimiter=NL, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| open_ai.set_max_tokens(100).set_prompt(prompt) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| summary = open_ai.complete() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.summary = summary if summary and summary != "None" else "" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except (ValueError, TypeError): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.summary = "" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return self.summary | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def get_geo_string(self, include_name=True): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Return geo string.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return join_values( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| f"Name: {self.name}", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| f"Description: {self.description}", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| f"Summary: {self.summary}", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| delimiter=NL, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def generate_geo_location(self): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Add latitude and longitude data.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| location = None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if self.suggested_location and self.suggested_location != "None": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| location = get_location_coordinates(self.suggested_location) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if location is None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| location = get_location_coordinates(self.get_geo_string()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if location: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.latitude = location.latitude | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.longitude = location.longitude | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def generate_suggested_location(self, open_ai=None, max_tokens=100): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Generate project summary.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not (prompt := Prompt.get_owasp_event_suggested_location()): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| location = get_location_coordinates(self.suggested_location) | |
| if location is None: | |
| location = get_location_coordinates(self.get_geo_string()) | |
| if location: | |
| self.latitude = location.latitude | |
| self.longitude = location.longitude | |
| def generate_suggested_location(self, open_ai=None, max_tokens=100): | |
| """Generate project summary.""" | |
| if not (prompt := Prompt.get_owasp_event_suggested_location()): | |
| return | |
| location = get_location_coordinates(self.suggested_location) | |
| if location is None: | |
| location = get_location_coordinates(self.get_geo_string()) | |
| if location: | |
| self.latitude = location.latitude | |
| self.longitude = location.longitude | |
| def generate_suggested_location(self, open_ai=None, max_tokens=100): | |
| """Generate a suggested location for the event using OpenAI.""" | |
| prompt = Prompt.get_owasp_event_suggested_location() | |
| if not prompt: | |
| return | |
| open_ai = open_ai or OpenAi() | |
| open_ai.set_input(self.get_geo_string()) | |
| open_ai.set_max_tokens(max_tokens).set_prompt(prompt) | |
| try: | |
| suggested_location = open_ai.complete() | |
| self.suggested_location = ( | |
| suggested_location if suggested_location and suggested_location != "None" else "" | |
| ) | |
| except (ValueError, TypeError): | |
| self.suggested_location = "" |
Uh oh!
There was an error while loading. Please reload this page.