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

39 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-11 00:40 +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, 

21 choices=SUGGESTION_TYPES, 

22 help_text='Type of discovery suggestion' 

23 ) 

24 search_query = models.CharField( 

25 max_length=255, 

26 help_text='Search query to execute for this suggestion' 

27 ) 

28 title = models.CharField( 

29 max_length=255, 

30 help_text='Display title for this suggestion' 

31 ) 

32 description = models.TextField( 

33 help_text='Explanation of why this suggestion fits the user' 

34 ) 

35 created_at = models.DateTimeField(auto_now_add=True) 

36 

37 class Meta: 

38 ordering = ['-created_at'] 

39 verbose_name = 'AI Discovery Suggestion' 

40 verbose_name_plural = 'AI Discovery Suggestions' 

41 indexes = [ 

42 models.Index(fields=['profile', 'suggestion_type', 'created_at']), 

43 ] 

44 

45 def __str__(self): 

46 return f'{self.suggestion_type}: {self.title}' 

47 

48 

49class AIPrompt(models.Model): 

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

51 

52 PROMPT_TYPES = [ 

53 ('recipe_remix', 'Recipe Remix'), 

54 ('serving_adjustment', 'Serving Adjustment'), 

55 ('tips_generation', 'Tips Generation'), 

56 ('nutrition_estimate', 'Nutrition Estimate'), 

57 ('discover_favorites', 'Discover from Favorites'), 

58 ('discover_seasonal', 'Discover Seasonal/Holiday'), 

59 ('discover_new', 'Discover Try Something New'), 

60 ('search_ranking', 'Search Result Ranking'), 

61 ('timer_naming', 'Timer Naming'), 

62 ('remix_suggestions', 'Remix Suggestions'), 

63 ('selector_repair', 'CSS Selector Repair'), 

64 ] 

65 

66 AVAILABLE_MODELS = [ 

67 # Anthropic Claude 

68 ('anthropic/claude-3.5-haiku', 'Claude 3.5 Haiku (Fast)'), 

69 ('anthropic/claude-sonnet-4', 'Claude Sonnet 4'), 

70 ('anthropic/claude-opus-4', 'Claude Opus 4'), 

71 ('anthropic/claude-opus-4.5', 'Claude Opus 4.5'), 

72 # OpenAI GPT 

73 ('openai/gpt-4o', 'GPT-4o'), 

74 ('openai/gpt-4o-mini', 'GPT-4o Mini (Fast)'), 

75 ('openai/gpt-5-mini', 'GPT-5 Mini'), 

76 ('openai/o3-mini', 'o3 Mini (Reasoning)'), 

77 # Google Gemini 

78 ('google/gemini-2.5-pro-preview', 'Gemini 2.5 Pro'), 

79 ('google/gemini-2.5-flash-preview', 'Gemini 2.5 Flash (Fast)'), 

80 ] 

81 

82 prompt_type = models.CharField( 

83 max_length=50, 

84 choices=PROMPT_TYPES, 

85 unique=True, 

86 help_text='Unique identifier for this prompt type' 

87 ) 

88 name = models.CharField( 

89 max_length=100, 

90 help_text='Human-readable name for this prompt' 

91 ) 

92 description = models.TextField( 

93 blank=True, 

94 help_text='Description of what this prompt does' 

95 ) 

96 system_prompt = models.TextField( 

97 help_text='System message sent to the AI model' 

98 ) 

99 user_prompt_template = models.TextField( 

100 help_text='User message template with {placeholders} for variable substitution' 

101 ) 

102 model = models.CharField( 

103 max_length=100, 

104 choices=AVAILABLE_MODELS, 

105 default='anthropic/claude-3.5-haiku', 

106 help_text='AI model to use for this prompt' 

107 ) 

108 is_active = models.BooleanField( 

109 default=True, 

110 help_text='Whether this prompt is enabled' 

111 ) 

112 created_at = models.DateTimeField(auto_now_add=True) 

113 updated_at = models.DateTimeField(auto_now=True) 

114 

115 class Meta: 

116 ordering = ['prompt_type'] 

117 verbose_name = 'AI Prompt' 

118 verbose_name_plural = 'AI Prompts' 

119 

120 def __str__(self): 

121 return self.name 

122 

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

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

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

126 

127 @classmethod 

128 def get_prompt(cls, prompt_type: str) -> 'AIPrompt': 

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

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

← Back to Dashboard