rinogeek commited on
Commit
26c0cd1
·
1 Parent(s): 4e138bb
api/gemini_service.py CHANGED
@@ -28,13 +28,15 @@ class GeminiService:
28
  # Use free tier model with generous quotas
29
  self.model = "gemini-flash-latest" # Free tier: 15 RPM, 1M tokens/day
30
 
31
- def process_voice_command(self, audio_bytes, mime_type="audio/mp3"):
32
  """
33
  Process audio bytes to extract transaction details.
34
  Returns a dictionary with transcription and structured data.
35
  """
36
-
37
- prompt = """
 
 
38
  You are an AI assistant for a financial app called Akompta.
39
  Your task is to listen to the user's voice command and extract transaction details.
40
 
@@ -42,30 +44,37 @@ class GeminiService:
42
  - "J'ai vendu la tomate pour 500FCFA le Kilo" (Income)
43
  - "J'ai payé un ordinateur à 300000FCFA" (Expense)
44
 
 
 
 
 
 
45
  Please perform the following:
46
  1. Transcribe the audio exactly as spoken (in French).
47
  2. Analyze the intent and extract structured data.
48
 
49
  Return ONLY a JSON object with the following structure:
50
- {
51
  "transcription": "The exact transcription",
52
  "intent": "create_transaction",
53
- "data": {
54
  "type": "income" or "expense",
55
  "amount": number (e.g. 500),
56
  "currency": "FCFA" or other,
57
  "category": "Category name (e.g. Vente, Alimentation, Transport, Technologie)",
58
- "name": "Description of the item or service",
59
  "date": "YYYY-MM-DD" or null if not specified (assume today if null)
60
- }
61
- }
 
 
62
 
63
  If the audio is not clear or not related to a transaction, return:
64
- {
65
  "transcription": "...",
66
  "intent": "unknown",
67
  "error": "Reason"
68
- }
69
  """
70
 
71
  try:
@@ -96,11 +105,14 @@ class GeminiService:
96
  "error": str(e)
97
  }
98
 
99
- def process_text_command(self, text):
100
  """
101
  Process text input to extract transaction details.
102
  """
103
- prompt = """
 
 
 
104
  You are an AI assistant for a financial app called Akompta.
105
  Your task is to analyze the user's text command and extract transaction details.
106
 
@@ -109,45 +121,53 @@ class GeminiService:
109
  - "J'ai payé un ordinateur à 300000FCFA" (Expense)
110
  - "Ajoute un produit Tomate à 200FCFA le bol, j'en ai 30 en stock" (Create Product)
111
 
 
 
 
 
 
 
112
  Please perform the following:
113
  1. Analyze the intent and extract structured data.
114
 
115
  Return ONLY a JSON object with the following structure:
116
 
117
  For transactions:
118
- {
119
  "transcription": "The input text",
120
  "intent": "create_transaction",
121
- "data": {
122
  "type": "income" or "expense",
123
  "amount": number,
124
  "currency": "FCFA",
125
  "category": "Category name",
126
- "name": "Description",
127
  "date": "YYYY-MM-DD"
128
- }
129
- }
 
 
130
 
131
  For products/inventory:
132
- {
133
  "transcription": "The input text",
134
  "intent": "create_product",
135
- "data": {
136
  "name": "Product name",
137
  "price": number,
138
  "unit": "Unit (e.g. bol, kg, unit)",
139
  "description": "Short description",
140
  "category": "vente" or "depense" or "stock",
141
  "stock_status": "ok" or "low" or "rupture"
142
- }
143
- }
144
 
145
  If the text is not clear, return:
146
- {
147
  "transcription": "...",
148
  "intent": "unknown",
149
  "error": "Reason"
150
- }
151
  """
152
 
153
  try:
 
28
  # Use free tier model with generous quotas
29
  self.model = "gemini-flash-latest" # Free tier: 15 RPM, 1M tokens/day
30
 
31
+ def process_voice_command(self, audio_bytes, mime_type="audio/mp3", context_products=None):
32
  """
33
  Process audio bytes to extract transaction details.
34
  Returns a dictionary with transcription and structured data.
35
  """
36
+ if context_products is None:
37
+ context_products = []
38
+
39
+ prompt = f"""
40
  You are an AI assistant for a financial app called Akompta.
41
  Your task is to listen to the user's voice command and extract transaction details.
42
 
 
44
  - "J'ai vendu la tomate pour 500FCFA le Kilo" (Income)
45
  - "J'ai payé un ordinateur à 300000FCFA" (Expense)
46
 
47
+ Here is the list of products currently in the user's inventory:
48
+ {json.dumps(context_products)}
49
+
50
+ If the user mentions a product from this list but doesn't specify the price, use the price from the list to calculate the total amount (amount = quantity * price).
51
+
52
  Please perform the following:
53
  1. Transcribe the audio exactly as spoken (in French).
54
  2. Analyze the intent and extract structured data.
55
 
56
  Return ONLY a JSON object with the following structure:
57
+ {{
58
  "transcription": "The exact transcription",
59
  "intent": "create_transaction",
60
+ "data": {{
61
  "type": "income" or "expense",
62
  "amount": number (e.g. 500),
63
  "currency": "FCFA" or other,
64
  "category": "Category name (e.g. Vente, Alimentation, Transport, Technologie)",
65
+ "name": "Description of the item or service (e.g. 'Vente de 3 Mangues')",
66
  "date": "YYYY-MM-DD" or null if not specified (assume today if null)
67
+ }}
68
+ }}
69
+
70
+ Important: If a product price is found in the inventory list, ALWAYS calculate: amount = quantity * unit_price.
71
 
72
  If the audio is not clear or not related to a transaction, return:
73
+ {{
74
  "transcription": "...",
75
  "intent": "unknown",
76
  "error": "Reason"
77
+ }}
78
  """
79
 
80
  try:
 
105
  "error": str(e)
106
  }
107
 
108
+ def process_text_command(self, text, context_products=None):
109
  """
110
  Process text input to extract transaction details.
111
  """
112
+ if context_products is None:
113
+ context_products = []
114
+
115
+ prompt = f"""
116
  You are an AI assistant for a financial app called Akompta.
117
  Your task is to analyze the user's text command and extract transaction details.
118
 
 
121
  - "J'ai payé un ordinateur à 300000FCFA" (Expense)
122
  - "Ajoute un produit Tomate à 200FCFA le bol, j'en ai 30 en stock" (Create Product)
123
 
124
+ Here is the list of products currently in the user's inventory:
125
+ {json.dumps(context_products)}
126
+
127
+ If the user mentions a product from this list but doesn't specify the price, use the price from the list to calculate the total amount (amount = quantity * price).
128
+ Example: If "Mangue" is in the list at 100 FCFA and the user says "vendu 3 mangues", the amount should be 300.
129
+
130
  Please perform the following:
131
  1. Analyze the intent and extract structured data.
132
 
133
  Return ONLY a JSON object with the following structure:
134
 
135
  For transactions:
136
+ {{
137
  "transcription": "The input text",
138
  "intent": "create_transaction",
139
+ "data": {{
140
  "type": "income" or "expense",
141
  "amount": number,
142
  "currency": "FCFA",
143
  "category": "Category name",
144
+ "name": "Description (e.g. 'Vente de 3 Mangues')",
145
  "date": "YYYY-MM-DD"
146
+ }}
147
+ }}
148
+
149
+ Important: If a product price is found in the inventory list, ALWAYS calculate: amount = quantity * unit_price.
150
 
151
  For products/inventory:
152
+ {{
153
  "transcription": "The input text",
154
  "intent": "create_product",
155
+ "data": {{
156
  "name": "Product name",
157
  "price": number,
158
  "unit": "Unit (e.g. bol, kg, unit)",
159
  "description": "Short description",
160
  "category": "vente" or "depense" or "stock",
161
  "stock_status": "ok" or "low" or "rupture"
162
+ }}
163
+ }}
164
 
165
  If the text is not clear, return:
166
+ {{
167
  "transcription": "...",
168
  "intent": "unknown",
169
  "error": "Reason"
170
+ }}
171
  """
172
 
173
  try:
api/migrations/0005_user_initial_balance.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 5.2.8 on 2026-01-22 21:34
2
+
3
+ from decimal import Decimal
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ('api', '0004_aiinsight'),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.AddField(
15
+ model_name='user',
16
+ name='initial_balance',
17
+ field=models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=15),
18
+ ),
19
+ ]
api/models.py CHANGED
@@ -53,6 +53,7 @@ class User(AbstractUser):
53
  default='personal'
54
  )
55
  is_premium = models.BooleanField(default=False)
 
56
 
57
  # Champs Business
58
  business_name = models.CharField(max_length=255, blank=True)
 
53
  default='personal'
54
  )
55
  is_premium = models.BooleanField(default=False)
56
+ initial_balance = models.DecimalField(max_digits=15, decimal_places=2, default=Decimal('0.00'))
57
 
58
  # Champs Business
59
  business_name = models.CharField(max_length=255, blank=True)
api/serializers.py CHANGED
@@ -14,7 +14,7 @@ class UserSerializer(serializers.ModelSerializer):
14
  model = User
15
  fields = [
16
  'id', 'email', 'first_name', 'last_name', 'phone_number',
17
- 'avatar', 'account_type', 'is_premium',
18
  'business_name', 'sector', 'location', 'ifu', 'business_logo',
19
  'currency', 'language', 'dark_mode',
20
  'created_at', 'updated_at'
 
14
  model = User
15
  fields = [
16
  'id', 'email', 'first_name', 'last_name', 'phone_number',
17
+ 'avatar', 'account_type', 'is_premium', 'initial_balance',
18
  'business_name', 'sector', 'location', 'ifu', 'business_logo',
19
  'currency', 'language', 'dark_mode',
20
  'created_at', 'updated_at'
api/views.py CHANGED
@@ -285,7 +285,7 @@ class TransactionViewSet(viewsets.ModelViewSet):
285
  user=user, type='expense'
286
  ).aggregate(total=Sum('amount'))['total'] or Decimal('0.00')
287
 
288
- balance = total_income - total_expenses
289
 
290
  # Variations en %
291
  def calc_variation(current, previous):
@@ -449,7 +449,7 @@ class AnalyticsView(APIView):
449
  transactions = Transaction.objects.filter(user=user).order_by('date')
450
 
451
  history = []
452
- running_balance = Decimal('0.00')
453
 
454
  # Grouper par date pour éviter d'avoir trop de points si plusieurs transactions le même jour
455
  daily_balances = {}
@@ -598,12 +598,20 @@ class VoiceCommandView(APIView):
598
  try:
599
  service = GeminiService()
600
 
 
 
 
 
 
 
 
 
601
  if audio_file:
602
  audio_bytes = audio_file.read()
603
  mime_type = audio_file.content_type or 'audio/mp3'
604
- result = service.process_voice_command(audio_bytes, mime_type)
605
  else:
606
- result = service.process_text_command(text_command)
607
 
608
  print(f"VoiceCommandView - Result Intent: {result.get('intent')}")
609
 
 
285
  user=user, type='expense'
286
  ).aggregate(total=Sum('amount'))['total'] or Decimal('0.00')
287
 
288
+ balance = user.initial_balance + total_income - total_expenses
289
 
290
  # Variations en %
291
  def calc_variation(current, previous):
 
449
  transactions = Transaction.objects.filter(user=user).order_by('date')
450
 
451
  history = []
452
+ running_balance = user.initial_balance
453
 
454
  # Grouper par date pour éviter d'avoir trop de points si plusieurs transactions le même jour
455
  daily_balances = {}
 
598
  try:
599
  service = GeminiService()
600
 
601
+ # Fetch user products for context
602
+ user_products = Product.objects.filter(user=request.user)
603
+ products_list = [
604
+ {"name": p.name, "price": float(p.price), "unit": p.unit}
605
+ for p in user_products
606
+ ]
607
+ print(f"VoiceCommandView - Context Products: {products_list}")
608
+
609
  if audio_file:
610
  audio_bytes = audio_file.read()
611
  mime_type = audio_file.content_type or 'audio/mp3'
612
+ result = service.process_voice_command(audio_bytes, mime_type, context_products=products_list)
613
  else:
614
+ result = service.process_text_command(text_command, context_products=products_list)
615
 
616
  print(f"VoiceCommandView - Result Intent: {result.get('intent')}")
617