GitHub Actions commited on
Commit
eb803dd
·
1 Parent(s): 198c5a7

🚀 Auto-deploy from GitHub

Browse files
.vscode/settings.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "python.analysis.extraPaths": [
3
+ "./app"
4
+ ]
5
+ }
app/api/v1/endpoints/generate.py CHANGED
@@ -1,4 +1,4 @@
1
- from fastapi import APIRouter, HTTPException, Depends
2
  from dotenv import load_dotenv
3
  from supabase import Client
4
  import uuid
@@ -12,6 +12,8 @@ from ....core.config import settings
12
  from ....core.model_loader import get_generator
13
  from ....core.constraints import generate_with_retry, check_constraints
14
  from ....core.auth import authenticate_request, get_current_user
 
 
15
  from fastapi.concurrency import run_in_threadpool # Importieren
16
 
17
  load_dotenv()
@@ -28,11 +30,18 @@ async def generate_qr_code_async(*args, **kwargs):
28
  @router.post("/generate", response_model=CardGenerateResponse)
29
  async def generate_endpoint(
30
  request: CardGenerateRequest,
 
31
  supabase: Client = Depends(get_supabase_client),
32
  authenticated: bool = Depends(authenticate_request),
33
  current_user: Optional[Dict[str, Any]] = Depends(get_current_user)
34
  ):
35
  try:
 
 
 
 
 
 
36
  lang = request.lang or "de"
37
  input_date_str = request.card_date.isoformat()
38
 
@@ -40,10 +49,25 @@ async def generate_endpoint(
40
  user_id = current_user["id"] if current_user else None
41
  username = current_user["username"] if current_user else "anonymous"
42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  card_prompt = build_prompt(
44
  lang=lang,
45
  card_date=input_date_str,
46
- terms=request.terms
 
47
  )
48
 
49
  llm_pipeline = get_generator()
@@ -62,13 +86,23 @@ async def generate_endpoint(
62
  generator=llm_pipeline,
63
  terms=request.terms,
64
  max_retries=settings.GENERATION_MAX_RETRIES,
65
- generation_params=generation_params
 
66
  )
67
 
68
  if card_text == "Leider konnte kein gültiger Text erzeugt werden." or not check_constraints(card_text, request.terms):
 
 
 
 
 
 
 
 
 
69
  raise HTTPException(
70
  status_code=500,
71
- detail="Kartentext konnte nicht generiert werden oder erfüllt nicht die Bedingungen."
72
  )
73
 
74
  card_id_for_url = str(uuid.uuid4())
@@ -91,7 +125,8 @@ async def generate_endpoint(
91
  base_images_path=settings.resolved_base_path,
92
  symbols_images_path=settings.resolved_symbols_path,
93
  font_path=settings.resolved_default_font_path,
94
- output_path=settings.resolved_generated_path
 
95
  )
96
 
97
  card_data_for_db = {
@@ -115,18 +150,52 @@ async def generate_endpoint(
115
  elif isinstance(db_response, list) and db_response and isinstance(db_response[0], dict):
116
  db_id = str(db_response[0].get('id'))
117
 
 
 
 
 
 
 
 
 
 
 
118
  return CardGenerateResponse(
119
  message="Karte erfolgreich generiert.",
120
  card_id=db_id if db_id else card_id_for_url,
121
  qr_code_image_url=qr_code_url
122
  )
123
  except FileNotFoundError as e:
 
124
  print(f"FileNotFoundError in generate_endpoint: {e}")
125
- raise HTTPException(status_code=500, detail=f"Ein benötigtes Template oder eine Datei wurde nicht gefunden: {e.filename}")
 
 
 
 
 
 
 
 
126
  except HTTPException as e:
 
 
 
 
 
 
127
  raise e
128
  except Exception as e:
 
129
  import traceback
130
  print(f"Unerwarteter Fehler in generate_endpoint: {type(e).__name__} - {str(e)}")
131
  traceback.print_exc()
132
- raise HTTPException(status_code=500, detail=f"Ein interner Fehler ist aufgetreten: {str(e)}")
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, HTTPException, Depends, Request
2
  from dotenv import load_dotenv
3
  from supabase import Client
4
  import uuid
 
12
  from ....core.model_loader import get_generator
13
  from ....core.constraints import generate_with_retry, check_constraints
14
  from ....core.auth import authenticate_request, get_current_user
15
+ from ....services.request_logger import RequestEventLogger
16
+ from ....services.auth_logger import get_client_info
17
  from fastapi.concurrency import run_in_threadpool # Importieren
18
 
19
  load_dotenv()
 
30
  @router.post("/generate", response_model=CardGenerateResponse)
31
  async def generate_endpoint(
32
  request: CardGenerateRequest,
33
+ fastapi_request: Request,
34
  supabase: Client = Depends(get_supabase_client),
35
  authenticated: bool = Depends(authenticate_request),
36
  current_user: Optional[Dict[str, Any]] = Depends(get_current_user)
37
  ):
38
  try:
39
+ # Generate unique request ID for tracking
40
+ request_id = str(uuid.uuid4())
41
+
42
+ # Extract client information
43
+ client_ip, user_agent = get_client_info(fastapi_request)
44
+
45
  lang = request.lang or "de"
46
  input_date_str = request.card_date.isoformat()
47
 
 
49
  user_id = current_user["id"] if current_user else None
50
  username = current_user["username"] if current_user else "anonymous"
51
 
52
+ # Log incoming request
53
+ RequestEventLogger.log_card_generation_request(
54
+ user_id=user_id,
55
+ username=username,
56
+ client_ip=client_ip,
57
+ user_agent=user_agent,
58
+ terms=request.terms,
59
+ card_date=input_date_str,
60
+ lang=lang,
61
+ card_design_id=request.card_design_id_override,
62
+ symbol_ids=request.symbol_ids_override,
63
+ request_id=request_id
64
+ )
65
+
66
  card_prompt = build_prompt(
67
  lang=lang,
68
  card_date=input_date_str,
69
+ terms=request.terms,
70
+ request_id=request_id
71
  )
72
 
73
  llm_pipeline = get_generator()
 
86
  generator=llm_pipeline,
87
  terms=request.terms,
88
  max_retries=settings.GENERATION_MAX_RETRIES,
89
+ generation_params=generation_params,
90
+ request_id=request_id
91
  )
92
 
93
  if card_text == "Leider konnte kein gültiger Text erzeugt werden." or not check_constraints(card_text, request.terms):
94
+ error_msg = "Kartentext konnte nicht generiert werden oder erfüllt nicht die Bedingungen."
95
+
96
+ # Log failed card generation
97
+ RequestEventLogger.log_card_generation_result(
98
+ request_id=request_id,
99
+ success=False,
100
+ error_message=error_msg
101
+ )
102
+
103
  raise HTTPException(
104
  status_code=500,
105
+ detail=error_msg
106
  )
107
 
108
  card_id_for_url = str(uuid.uuid4())
 
125
  base_images_path=settings.resolved_base_path,
126
  symbols_images_path=settings.resolved_symbols_path,
127
  font_path=settings.resolved_default_font_path,
128
+ output_path=settings.resolved_generated_path,
129
+ request_id=request_id
130
  )
131
 
132
  card_data_for_db = {
 
150
  elif isinstance(db_response, list) and db_response and isinstance(db_response[0], dict):
151
  db_id = str(db_response[0].get('id'))
152
 
153
+ # Log successful card generation
154
+ RequestEventLogger.log_card_generation_result(
155
+ request_id=request_id,
156
+ success=True,
157
+ card_id=db_id if db_id else card_id_for_url,
158
+ card_file_id=card_file_id,
159
+ qr_code_file_id=qr_code_file_id,
160
+ qr_content_url=qr_content_url
161
+ )
162
+
163
  return CardGenerateResponse(
164
  message="Karte erfolgreich generiert.",
165
  card_id=db_id if db_id else card_id_for_url,
166
  qr_code_image_url=qr_code_url
167
  )
168
  except FileNotFoundError as e:
169
+ error_msg = f"Ein benötigtes Template oder eine Datei wurde nicht gefunden: {e.filename}"
170
  print(f"FileNotFoundError in generate_endpoint: {e}")
171
+
172
+ # Log failed card generation
173
+ RequestEventLogger.log_card_generation_result(
174
+ request_id=request_id,
175
+ success=False,
176
+ error_message=error_msg
177
+ )
178
+
179
+ raise HTTPException(status_code=500, detail=error_msg)
180
  except HTTPException as e:
181
+ # Log failed card generation for HTTP exceptions
182
+ RequestEventLogger.log_card_generation_result(
183
+ request_id=request_id if 'request_id' in locals() else None,
184
+ success=False,
185
+ error_message=str(e.detail)
186
+ )
187
  raise e
188
  except Exception as e:
189
+ error_msg = f"Ein interner Fehler ist aufgetreten: {str(e)}"
190
  import traceback
191
  print(f"Unerwarteter Fehler in generate_endpoint: {type(e).__name__} - {str(e)}")
192
  traceback.print_exc()
193
+
194
+ # Log failed card generation
195
+ RequestEventLogger.log_card_generation_result(
196
+ request_id=request_id if 'request_id' in locals() else None,
197
+ success=False,
198
+ error_message=error_msg
199
+ )
200
+
201
+ raise HTTPException(status_code=500, detail=error_msg)
app/core/card_renderer.py CHANGED
@@ -2,6 +2,20 @@ from PIL import Image, ImageDraw, ImageFont
2
  import uuid
3
  from pathlib import Path
4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  def generate_card(
6
  card_design_id: int,
7
  symbol_ids: list[int],
@@ -9,13 +23,28 @@ def generate_card(
9
  base_images_path: Path,
10
  symbols_images_path: Path,
11
  font_path: Path,
12
- output_path: Path
 
13
  ) -> str:
14
  """
15
  Generiert eine Karte und speichert sie.
16
  Verwendet jetzt übergebene Pfade für mehr Flexibilität und Testbarkeit.
17
  Gibt die UUID der generierten Datei (ohne Erweiterung) zurück.
18
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  try:
20
  # Basiskarte laden
21
  base_image_file = base_images_path / f"base{card_design_id}.png"
 
2
  import uuid
3
  from pathlib import Path
4
 
5
+ # Handle import for both package and direct execution
6
+ try:
7
+ from ..services.request_logger import RequestEventLogger
8
+ except ImportError:
9
+ # If relative import fails, try absolute import or create a dummy logger
10
+ try:
11
+ from app.services.request_logger import RequestEventLogger
12
+ except ImportError:
13
+ # Create a dummy logger class if import fails
14
+ class RequestEventLogger:
15
+ @staticmethod
16
+ def log_card_renderer_params(*args, **kwargs):
17
+ pass
18
+
19
  def generate_card(
20
  card_design_id: int,
21
  symbol_ids: list[int],
 
23
  base_images_path: Path,
24
  symbols_images_path: Path,
25
  font_path: Path,
26
+ output_path: Path,
27
+ request_id: str | None = None
28
  ) -> str:
29
  """
30
  Generiert eine Karte und speichert sie.
31
  Verwendet jetzt übergebene Pfade für mehr Flexibilität und Testbarkeit.
32
  Gibt die UUID der generierten Datei (ohne Erweiterung) zurück.
33
  """
34
+
35
+ # Log card renderer parameters
36
+ if request_id:
37
+ RequestEventLogger.log_card_renderer_params(
38
+ request_id=request_id,
39
+ card_design_id=card_design_id,
40
+ symbol_ids=symbol_ids,
41
+ text=text,
42
+ base_images_path=str(base_images_path),
43
+ symbols_images_path=str(symbols_images_path),
44
+ font_path=str(font_path),
45
+ output_path=str(output_path)
46
+ )
47
+
48
  try:
49
  # Basiskarte laden
50
  base_image_file = base_images_path / f"base{card_design_id}.png"
app/core/config.py CHANGED
@@ -40,7 +40,8 @@ class Settings(BaseSettings):
40
  GENERATION_TOP_P: float = 0.95
41
  GENERATION_MAX_RETRIES: int = 3
42
 
43
- FRONTEND_BASE_URL: str = "http://localhost:3000"
 
44
  QR_CODE_SIZE: int = 200
45
  QR_CODE_BOX_SIZE: int = 10
46
  QR_CODE_BORDER: int = 4
 
40
  GENERATION_TOP_P: float = 0.95
41
  GENERATION_MAX_RETRIES: int = 3
42
 
43
+ # Frontend URL for QR codes - should be set to production URL in production
44
+ FRONTEND_BASE_URL: str = os.getenv("FRONTEND_BASE_URL", "https://huggingface.co/spaces/ch404/cardserver")
45
  QR_CODE_SIZE: int = 200
46
  QR_CODE_BOX_SIZE: int = 10
47
  QR_CODE_BORDER: int = 4
app/core/constraints.py CHANGED
@@ -3,32 +3,93 @@
3
  import asyncio
4
  from fastapi.concurrency import run_in_threadpool
5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  def check_constraints(output: str, terms: list[str]) -> bool:
7
  if not terms:
8
  return True
9
  return all(term.lower() in output.lower() for term in terms)
10
 
11
- async def generate_with_retry(prompt: str, generator, terms: list[str], max_retries: int = 3, generation_params: dict | None = None): # async def
12
  """
13
  Generiert Text mit Wiederholungsversuchen, bis die Bedingungen (Constraints) erfüllt sind.
14
  Führt die eigentliche Generierung in einem Threadpool aus.
15
  """
16
  if generation_params is None:
17
  generation_params = {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
  for attempt in range(max_retries):
20
  try:
21
  responses = await run_in_threadpool(generator, prompt, **generation_params)
22
  if responses and isinstance(responses, list) and responses[0].get("generated_text"):
23
  generated_text = responses[0]["generated_text"].strip()
24
- if check_constraints(generated_text, terms):
 
 
 
 
 
 
 
 
 
 
 
25
  return generated_text
26
  else:
27
  print(f"Unerwartete Antwortstruktur vom Generator bei Versuch {attempt + 1}: {responses}")
28
- generated_text = ""
 
 
 
 
 
 
 
 
 
29
 
30
  except Exception as e:
31
  print(f"Fehler bei der Textgenerierung (Versuch {attempt + 1}/{max_retries}): {e}")
 
 
 
 
 
 
 
 
 
 
32
  await asyncio.sleep(0.5)
33
 
34
  return "Leider konnte kein gültiger Text erzeugt werden."
 
3
  import asyncio
4
  from fastapi.concurrency import run_in_threadpool
5
 
6
+ # Handle import for both package and direct execution
7
+ try:
8
+ from ..services.request_logger import RequestEventLogger
9
+ except ImportError:
10
+ # If relative import fails, try absolute import or create a dummy logger
11
+ try:
12
+ from app.services.request_logger import RequestEventLogger
13
+ except ImportError:
14
+ # Create a dummy logger class if import fails
15
+ class RequestEventLogger:
16
+ @staticmethod
17
+ def log_llm_request(*args, **kwargs):
18
+ pass
19
+ @staticmethod
20
+ def log_llm_response(*args, **kwargs):
21
+ pass
22
+
23
  def check_constraints(output: str, terms: list[str]) -> bool:
24
  if not terms:
25
  return True
26
  return all(term.lower() in output.lower() for term in terms)
27
 
28
+ async def generate_with_retry(prompt: str, generator, terms: list[str], max_retries: int = 3, generation_params: dict | None = None, request_id: str | None = None): # async def
29
  """
30
  Generiert Text mit Wiederholungsversuchen, bis die Bedingungen (Constraints) erfüllt sind.
31
  Führt die eigentliche Generierung in einem Threadpool aus.
32
  """
33
  if generation_params is None:
34
  generation_params = {}
35
+
36
+ # Log LLM request
37
+ model_info = {"generator_type": str(type(generator).__name__)}
38
+ if hasattr(generator, 'model'):
39
+ if hasattr(generator.model, 'config'):
40
+ model_info.update(generator.model.config.to_dict() if hasattr(generator.model.config, 'to_dict') else {"config": str(generator.model.config)})
41
+ model_info["model_name"] = str(type(generator.model).__name__)
42
+
43
+ RequestEventLogger.log_llm_request(
44
+ request_id=request_id,
45
+ prompt=prompt,
46
+ generation_params=generation_params,
47
+ model_info=model_info
48
+ )
49
 
50
  for attempt in range(max_retries):
51
  try:
52
  responses = await run_in_threadpool(generator, prompt, **generation_params)
53
  if responses and isinstance(responses, list) and responses[0].get("generated_text"):
54
  generated_text = responses[0]["generated_text"].strip()
55
+ constraints_met = check_constraints(generated_text, terms)
56
+
57
+ # Log LLM response
58
+ RequestEventLogger.log_llm_response(
59
+ request_id=request_id,
60
+ generated_text=generated_text,
61
+ attempt=attempt + 1,
62
+ constraints_met=constraints_met,
63
+ terms=terms
64
+ )
65
+
66
+ if constraints_met:
67
  return generated_text
68
  else:
69
  print(f"Unerwartete Antwortstruktur vom Generator bei Versuch {attempt + 1}: {responses}")
70
+ generated_text = ""
71
+
72
+ # Log failed response
73
+ RequestEventLogger.log_llm_response(
74
+ request_id=request_id,
75
+ generated_text="",
76
+ attempt=attempt + 1,
77
+ constraints_met=False,
78
+ terms=terms
79
+ )
80
 
81
  except Exception as e:
82
  print(f"Fehler bei der Textgenerierung (Versuch {attempt + 1}/{max_retries}): {e}")
83
+
84
+ # Log error response
85
+ RequestEventLogger.log_llm_response(
86
+ request_id=request_id,
87
+ generated_text=f"ERROR: {str(e)}",
88
+ attempt=attempt + 1,
89
+ constraints_met=False,
90
+ terms=terms
91
+ )
92
+
93
  await asyncio.sleep(0.5)
94
 
95
  return "Leider konnte kein gültiger Text erzeugt werden."
app/core/generator.py CHANGED
@@ -2,6 +2,20 @@ from jinja2 import Template
2
  from pathlib import Path
3
  import datetime
4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  """ MMDD → MM = Monat, DD = Tag
6
  120 = 01.20. → Ende Steinbock
7
  218 = 02.18. → Ende Wassermann
@@ -33,13 +47,27 @@ Der Text soll die Schlüsselbegriffe kreativ und subtil einbinden und zum angege
33
  Antworte nur mit dem Horoskoptext.
34
  """)
35
 
36
- def build_prompt(lang: str, card_date: str, terms: list[str]) -> str:
37
  """Baut den Prompt für das LLM basierend auf Geburtsdatum und Begriffen."""
38
  constellation = get_constellation(card_date)
39
  terms_str = ", ".join(terms)
40
- return PROMPT_TEMPLATE.render(
 
41
  lang=lang,
42
  card_date=card_date,
43
  constellation=constellation,
44
  terms_str=terms_str
45
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  from pathlib import Path
3
  import datetime
4
 
5
+ # Handle import for both package and direct execution
6
+ try:
7
+ from ..services.request_logger import RequestEventLogger
8
+ except ImportError:
9
+ # If relative import fails, try absolute import or create a dummy logger
10
+ try:
11
+ from app.services.request_logger import RequestEventLogger
12
+ except ImportError:
13
+ # Create a dummy logger class if import fails
14
+ class RequestEventLogger:
15
+ @staticmethod
16
+ def log_generator_prompt(*args, **kwargs):
17
+ pass
18
+
19
  """ MMDD → MM = Monat, DD = Tag
20
  120 = 01.20. → Ende Steinbock
21
  218 = 02.18. → Ende Wassermann
 
47
  Antworte nur mit dem Horoskoptext.
48
  """)
49
 
50
+ def build_prompt(lang: str, card_date: str, terms: list[str], request_id: str | None = None) -> str:
51
  """Baut den Prompt für das LLM basierend auf Geburtsdatum und Begriffen."""
52
  constellation = get_constellation(card_date)
53
  terms_str = ", ".join(terms)
54
+
55
+ prompt = PROMPT_TEMPLATE.render(
56
  lang=lang,
57
  card_date=card_date,
58
  constellation=constellation,
59
  terms_str=terms_str
60
+ )
61
+
62
+ # Log the generator prompt
63
+ if request_id:
64
+ RequestEventLogger.log_generator_prompt(
65
+ request_id=request_id,
66
+ prompt=prompt,
67
+ terms=terms,
68
+ card_date=card_date,
69
+ constellation=constellation,
70
+ lang=lang
71
+ )
72
+
73
+ return prompt
app/services/request_logger.py ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Request logging service for card generation requests
3
+ """
4
+
5
+ import logging
6
+ import json
7
+ from datetime import datetime
8
+ from typing import Optional, Dict, Any, List
9
+ from pathlib import Path
10
+
11
+ # Create logs directory if it doesn't exist
12
+ logs_dir = Path("logs")
13
+ logs_dir.mkdir(exist_ok=True)
14
+
15
+ # Create a specific logger for request events
16
+ request_logger = logging.getLogger("request_events")
17
+ request_logger.setLevel(logging.INFO)
18
+
19
+ # Create file handler for request logs
20
+ request_log_file = logs_dir / "request_events.log"
21
+ file_handler = logging.FileHandler(request_log_file)
22
+ file_handler.setLevel(logging.INFO)
23
+
24
+ # Create console handler for important events
25
+ console_handler = logging.StreamHandler()
26
+ console_handler.setLevel(logging.INFO)
27
+
28
+ # Create formatter for structured logging
29
+ formatter = logging.Formatter(
30
+ '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
31
+ datefmt='%Y-%m-%d %H:%M:%S'
32
+ )
33
+
34
+ file_handler.setFormatter(formatter)
35
+ console_handler.setFormatter(formatter)
36
+
37
+ # Add handlers to logger
38
+ request_logger.addHandler(file_handler)
39
+ request_logger.addHandler(console_handler)
40
+
41
+ # Prevent propagation to root logger to avoid duplicate logs
42
+ request_logger.propagate = False
43
+
44
+
45
+ class RequestEventLogger:
46
+ """Request event logging with structured file output"""
47
+
48
+ @staticmethod
49
+ def log_card_generation_request(
50
+ user_id: Optional[str],
51
+ username: str,
52
+ client_ip: str,
53
+ user_agent: str,
54
+ terms: List[str],
55
+ card_date: str,
56
+ lang: str,
57
+ card_design_id: Optional[int] = None,
58
+ symbol_ids: Optional[List[int]] = None,
59
+ request_id: Optional[str] = None
60
+ ):
61
+ """Log incoming card generation request"""
62
+ event_data = {
63
+ "event": "card_generation_request",
64
+ "request_id": request_id,
65
+ "user_id": user_id,
66
+ "username": username,
67
+ "client_ip": client_ip,
68
+ "user_agent": user_agent,
69
+ "terms": terms,
70
+ "card_date": card_date,
71
+ "lang": lang,
72
+ "card_design_id": card_design_id,
73
+ "symbol_ids": symbol_ids,
74
+ "timestamp": datetime.utcnow().isoformat()
75
+ }
76
+
77
+ request_logger.info(f"CARD_REQUEST: {json.dumps(event_data)}")
78
+
79
+ @staticmethod
80
+ def log_generator_prompt(
81
+ request_id: Optional[str],
82
+ prompt: str,
83
+ terms: List[str],
84
+ card_date: str,
85
+ constellation: str,
86
+ lang: str
87
+ ):
88
+ """Log the prompt sent to the generator"""
89
+ event_data = {
90
+ "event": "generator_prompt",
91
+ "request_id": request_id,
92
+ "prompt": prompt,
93
+ "terms": terms,
94
+ "card_date": card_date,
95
+ "constellation": constellation,
96
+ "lang": lang,
97
+ "timestamp": datetime.utcnow().isoformat()
98
+ }
99
+
100
+ request_logger.info(f"GENERATOR_PROMPT: {json.dumps(event_data)}")
101
+
102
+ @staticmethod
103
+ def log_llm_request(
104
+ request_id: Optional[str],
105
+ prompt: str,
106
+ generation_params: Dict[str, Any],
107
+ model_info: Dict[str, Any]
108
+ ):
109
+ """Log the request sent to the LLM via HF API"""
110
+ event_data = {
111
+ "event": "llm_request",
112
+ "request_id": request_id,
113
+ "prompt": prompt,
114
+ "generation_params": generation_params,
115
+ "model_info": model_info,
116
+ "timestamp": datetime.utcnow().isoformat()
117
+ }
118
+
119
+ request_logger.info(f"LLM_REQUEST: {json.dumps(event_data)}")
120
+
121
+ @staticmethod
122
+ def log_llm_response(
123
+ request_id: Optional[str],
124
+ generated_text: str,
125
+ attempt: int,
126
+ constraints_met: bool,
127
+ terms: List[str]
128
+ ):
129
+ """Log the response from the LLM"""
130
+ event_data = {
131
+ "event": "llm_response",
132
+ "request_id": request_id,
133
+ "generated_text": generated_text,
134
+ "attempt": attempt,
135
+ "constraints_met": constraints_met,
136
+ "terms": terms,
137
+ "timestamp": datetime.utcnow().isoformat()
138
+ }
139
+
140
+ request_logger.info(f"LLM_RESPONSE: {json.dumps(event_data)}")
141
+
142
+ @staticmethod
143
+ def log_card_renderer_params(
144
+ request_id: Optional[str],
145
+ card_design_id: int,
146
+ symbol_ids: List[int],
147
+ text: str,
148
+ base_images_path: str,
149
+ symbols_images_path: str,
150
+ font_path: str,
151
+ output_path: str
152
+ ):
153
+ """Log parameters sent to card renderer"""
154
+ event_data = {
155
+ "event": "card_renderer_params",
156
+ "request_id": request_id,
157
+ "card_design_id": card_design_id,
158
+ "symbol_ids": symbol_ids,
159
+ "text": text,
160
+ "base_images_path": str(base_images_path),
161
+ "symbols_images_path": str(symbols_images_path),
162
+ "font_path": str(font_path),
163
+ "output_path": str(output_path),
164
+ "timestamp": datetime.utcnow().isoformat()
165
+ }
166
+
167
+ request_logger.info(f"CARD_RENDERER_PARAMS: {json.dumps(event_data)}")
168
+
169
+ @staticmethod
170
+ def log_card_generation_result(
171
+ request_id: Optional[str],
172
+ success: bool,
173
+ card_id: Optional[str] = None,
174
+ card_file_id: Optional[str] = None,
175
+ qr_code_file_id: Optional[str] = None,
176
+ qr_content_url: Optional[str] = None,
177
+ error_message: Optional[str] = None
178
+ ):
179
+ """Log the final result of card generation"""
180
+ event_data = {
181
+ "event": "card_generation_result",
182
+ "request_id": request_id,
183
+ "success": success,
184
+ "card_id": card_id,
185
+ "card_file_id": card_file_id,
186
+ "qr_code_file_id": qr_code_file_id,
187
+ "qr_content_url": qr_content_url,
188
+ "error_message": error_message,
189
+ "timestamp": datetime.utcnow().isoformat()
190
+ }
191
+
192
+ if success:
193
+ request_logger.info(f"CARD_GENERATION_SUCCESS: {json.dumps(event_data)}")
194
+ else:
195
+ request_logger.error(f"CARD_GENERATION_FAILED: {json.dumps(event_data)}")
app/utils/qr_utils.py CHANGED
@@ -1,7 +1,20 @@
1
  import qrcode
2
  from pathlib import Path
3
  import uuid
4
- from ..core.config import settings
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
  def generate_qr_code_sync(data: str, output_path: Path, size: int) -> str:
7
  """
 
1
  import qrcode
2
  from pathlib import Path
3
  import uuid
4
+
5
+ # Handle import for both package and direct execution
6
+ try:
7
+ from ..core.config import settings
8
+ except ImportError:
9
+ # If relative import fails, try absolute import
10
+ try:
11
+ from app.core.config import settings
12
+ except ImportError:
13
+ # Create a minimal settings object if import fails
14
+ class MinimalSettings:
15
+ QR_CODE_BOX_SIZE = 10
16
+ QR_CODE_BORDER = 4
17
+ settings = MinimalSettings()
18
 
19
  def generate_qr_code_sync(data: str, output_path: Path, size: int) -> str:
20
  """
static/images/generated/04e27e5a-6abc-4e5d-8ec2-569a5c223783.png ADDED
static/images/generated/17af692c-d9b3-44f3-913a-361d14337636.png ADDED
static/images/qr/2c49e8a5-bf94-4b3a-977d-37b455b94f5b.png ADDED
static/images/qr/4a51cfca-5f0e-4433-81b5-e1e71ae7cc54.png ADDED
test_card_renderer.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ import sys
4
+ import traceback
5
+ from pathlib import Path
6
+
7
+ # Add the app directory to Python path
8
+ sys.path.insert(0, str(Path(__file__).parent / "app"))
9
+
10
+ try:
11
+ from core.card_renderer import generate_card
12
+ from core.config import settings
13
+
14
+ print('Testing card renderer...')
15
+
16
+ # Test parameters
17
+ card_design_id = 1
18
+ symbol_ids = [1, 2]
19
+ text = 'Test card text for debugging'
20
+
21
+ file_id = generate_card(
22
+ card_design_id=card_design_id,
23
+ symbol_ids=symbol_ids,
24
+ text=text,
25
+ base_images_path=settings.resolved_base_path,
26
+ symbols_images_path=settings.resolved_symbols_path,
27
+ font_path=settings.resolved_default_font_path,
28
+ output_path=settings.resolved_generated_path,
29
+ request_id='test-12345'
30
+ )
31
+
32
+ print(f'Success! Generated card with ID: {file_id}')
33
+
34
+ # Check if file was created
35
+ output_file = settings.resolved_generated_path / f'{file_id}.png'
36
+ print(f'File exists: {output_file.exists()}')
37
+ if output_file.exists():
38
+ print(f'File size: {output_file.stat().st_size} bytes')
39
+
40
+ except Exception as e:
41
+ print(f'Error: {type(e).__name__}: {str(e)}')
42
+ traceback.print_exc()
tests/test_card_renderer.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ import sys
4
+ import traceback
5
+ from pathlib import Path
6
+
7
+ # Add the app directory to Python path
8
+ sys.path.insert(0, str(Path(__file__).parent / "app"))
9
+
10
+ try:
11
+ from core.card_renderer import generate_card
12
+ from core.config import settings
13
+
14
+ print('Testing card renderer...')
15
+
16
+ # Test parameters
17
+ card_design_id = 1
18
+ symbol_ids = [1, 2]
19
+ text = 'Test card text for debugging'
20
+
21
+ file_id = generate_card(
22
+ card_design_id=card_design_id,
23
+ symbol_ids=symbol_ids,
24
+ text=text,
25
+ base_images_path=settings.resolved_base_path,
26
+ symbols_images_path=settings.resolved_symbols_path,
27
+ font_path=settings.resolved_default_font_path,
28
+ output_path=settings.resolved_generated_path,
29
+ request_id='test-12345'
30
+ )
31
+
32
+ print(f'Success! Generated card with ID: {file_id}')
33
+
34
+ # Check if file was created
35
+ output_file = settings.resolved_generated_path / f'{file_id}.png'
36
+ print(f'File exists: {output_file.exists()}')
37
+ if output_file.exists():
38
+ print(f'File size: {output_file.stat().st_size} bytes')
39
+
40
+ except Exception as e:
41
+ print(f'Error: {type(e).__name__}: {str(e)}')
42
+ traceback.print_exc()
tests/test_logging.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test script to verify logging functionality
4
+ """
5
+
6
+ import sys
7
+ import os
8
+
9
+ # Add the app directory to the Python path
10
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'app'))
11
+
12
+ from services.request_logger import RequestEventLogger
13
+ from datetime import datetime
14
+
15
+ def test_logging():
16
+ """Test the request logging functionality"""
17
+
18
+ print("Testing request logging...")
19
+
20
+ # Test initial request logging
21
+ RequestEventLogger.log_card_generation_request(
22
+ user_id="test-user-123",
23
+ username="test_user",
24
+ client_ip="127.0.0.1",
25
+ user_agent="Mozilla/5.0 (test)",
26
+ terms=["Liebe", "Abenteuer", "Erfolg"],
27
+ card_date="2025-06-17",
28
+ lang="de",
29
+ card_design_id=1,
30
+ symbol_ids=[1, 2, 3],
31
+ request_id="test-request-123"
32
+ )
33
+
34
+ # Test generator prompt logging
35
+ RequestEventLogger.log_generator_prompt(
36
+ request_id="test-request-123",
37
+ prompt="Du bist ein Sexoskop-Orakel...",
38
+ terms=["Liebe", "Abenteuer", "Erfolg"],
39
+ card_date="2025-06-17",
40
+ constellation="Zwillinge",
41
+ lang="de"
42
+ )
43
+
44
+ # Test LLM request logging
45
+ RequestEventLogger.log_llm_request(
46
+ request_id="test-request-123",
47
+ prompt="Du bist ein Sexoskop-Orakel...",
48
+ generation_params={
49
+ "max_new_tokens": 100,
50
+ "temperature": 0.7,
51
+ "do_sample": True
52
+ },
53
+ model_info={"model_name": "test-model", "version": "1.0"}
54
+ )
55
+
56
+ # Test LLM response logging
57
+ RequestEventLogger.log_llm_response(
58
+ request_id="test-request-123",
59
+ generated_text="Die Sterne stehen günstig für Liebe und Abenteuer.",
60
+ attempt=1,
61
+ constraints_met=True,
62
+ terms=["Liebe", "Abenteuer", "Erfolg"]
63
+ )
64
+
65
+ # Test card renderer params logging
66
+ RequestEventLogger.log_card_renderer_params(
67
+ request_id="test-request-123",
68
+ card_design_id=1,
69
+ symbol_ids=[1, 2, 3],
70
+ text="Die Sterne stehen günstig für Liebe und Abenteuer.",
71
+ base_images_path="/path/to/base/images",
72
+ symbols_images_path="/path/to/symbols",
73
+ font_path="/path/to/font",
74
+ output_path="/path/to/output"
75
+ )
76
+
77
+ # Test successful result logging
78
+ RequestEventLogger.log_card_generation_result(
79
+ request_id="test-request-123",
80
+ success=True,
81
+ card_id="card-123",
82
+ card_file_id="file-123",
83
+ qr_code_file_id="qr-123",
84
+ qr_content_url="https://example.com/card/123"
85
+ )
86
+
87
+ print("✅ Logging test completed successfully!")
88
+ print("📁 Check logs/request_events.log for the logged events")
89
+
90
+ if __name__ == "__main__":
91
+ test_logging()