Coverage for apps / ai / models.py: 90%

39 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-02-14 19:13 +0000

1from django.db import models 

2 

3 

4class AIDiscoverySuggestion(models.Model): 

5 """Caches AI-generated discovery suggestions per profile.""" 

6 

7 SUGGESTION_TYPES = [ 

8 ("favorites", "Based on Favorites"), 

9 ("seasonal", "Seasonal/Holiday"), 

10 ("new", "Try Something New"), 

11 ] 

12 

13 profile = models.ForeignKey( 

14 "profiles.Profile", 

15 on_delete=models.CASCADE, 

16 related_name="ai_discovery_suggestions", 

17 help_text="Profile this suggestion belongs to", 

18 ) 

19 suggestion_type = models.CharField( 

20 max_length=50, choices=SUGGESTION_TYPES, help_text="Type of discovery suggestion" 

21 ) 

22 search_query = models.CharField(max_length=255, help_text="Search query to execute for this suggestion") 

23 title = models.CharField(max_length=255, help_text="Display title for this suggestion") 

24 description = models.TextField(help_text="Explanation of why this suggestion fits the user") 

25 created_at = models.DateTimeField(auto_now_add=True) 

26 

27 class Meta: 

28 ordering = ["-created_at"] 

29 verbose_name = "AI Discovery Suggestion" 

30 verbose_name_plural = "AI Discovery Suggestions" 

31 indexes = [ 

32 models.Index(fields=["profile", "suggestion_type", "created_at"]), 

33 ] 

34 

35 def __str__(self): 

36 return f"{self.suggestion_type}: {self.title}" 

37 

38 

39class AIPrompt(models.Model): 

40 """Stores customizable AI prompts for various features.""" 

41 

42 PROMPT_TYPES = [ 

43 ("recipe_remix", "Recipe Remix"), 

44 ("serving_adjustment", "Serving Adjustment"), 

45 ("tips_generation", "Tips Generation"), 

46 ("nutrition_estimate", "Nutrition Estimate"), 

47 ("discover_favorites", "Discover from Favorites"), 

48 ("discover_seasonal", "Discover Seasonal/Holiday"), 

49 ("discover_new", "Discover Try Something New"), 

50 ("search_ranking", "Search Result Ranking"), 

51 ("timer_naming", "Timer Naming"), 

52 ("remix_suggestions", "Remix Suggestions"), 

53 ("selector_repair", "CSS Selector Repair"), 

54 ] 

55 

56 AVAILABLE_MODELS = [ 

57 # Anthropic Claude 

58 ("anthropic/claude-3.5-haiku", "Claude 3.5 Haiku (Fast)"), 

59 ("anthropic/claude-sonnet-4", "Claude Sonnet 4"), 

60 ("anthropic/claude-opus-4", "Claude Opus 4"), 

61 ("anthropic/claude-opus-4.5", "Claude Opus 4.5"), 

62 # OpenAI GPT 

63 ("openai/gpt-4o", "GPT-4o"), 

64 ("openai/gpt-4o-mini", "GPT-4o Mini (Fast)"), 

65 ("openai/gpt-5-mini", "GPT-5 Mini"), 

66 ("openai/o3-mini", "o3 Mini (Reasoning)"), 

67 # Google Gemini 

68 ("google/gemini-2.5-pro-preview", "Gemini 2.5 Pro"), 

69 ("google/gemini-2.5-flash-preview", "Gemini 2.5 Flash (Fast)"), 

70 ] 

71 

72 prompt_type = models.CharField( 

73 max_length=50, choices=PROMPT_TYPES, unique=True, help_text="Unique identifier for this prompt type" 

74 ) 

75 name = models.CharField(max_length=100, help_text="Human-readable name for this prompt") 

76 description = models.TextField(blank=True, help_text="Description of what this prompt does") 

77 system_prompt = models.TextField(help_text="System message sent to the AI model") 

78 user_prompt_template = models.TextField( 

79 help_text="User message template with {placeholders} for variable substitution" 

80 ) 

81 model = models.CharField( 

82 max_length=100, 

83 choices=AVAILABLE_MODELS, 

84 default="anthropic/claude-3.5-haiku", 

85 help_text="AI model to use for this prompt", 

86 ) 

87 is_active = models.BooleanField(default=True, help_text="Whether this prompt is enabled") 

88 created_at = models.DateTimeField(auto_now_add=True) 

89 updated_at = models.DateTimeField(auto_now=True) 

90 

91 class Meta: 

92 ordering = ["prompt_type"] 

93 verbose_name = "AI Prompt" 

94 verbose_name_plural = "AI Prompts" 

95 

96 def __str__(self): 

97 return self.name 

98 

99 def format_user_prompt(self, **kwargs) -> str: 

100 """Format the user prompt template with provided variables.""" 

101 return self.user_prompt_template.format(**kwargs) 

102 

103 @classmethod 

104 def get_prompt(cls, prompt_type: str) -> "AIPrompt": 

105 """Get an active prompt by type, raises DoesNotExist if not found.""" 

106 return cls.objects.get(prompt_type=prompt_type, is_active=True) 

← Back to Dashboard