match.py
Ricky/Cat Project Final/back_web-4/app/routes/match.py
import json
import logging
from flask import Blueprint, jsonify, request, current_app
from ..services.storage import Storage
from ..services.poe_analyze import (
analyze_with_poe,
generate_kitten_image
)
from ..utils.auth import require_jwt
logger = logging.getLogger(__name__)
bp = Blueprint("match", __name__, url_prefix="/api")
# ----------------------------
# Matching score logic
# ----------------------------
def _different_gender(my_gender: str, other_gender: str) -> bool:
"""Return True only if both genders are set and different (case-insensitive)."""
a = (my_gender or "").strip().lower()
b = (other_gender or "").strip().lower()
if not a or not b:
return False
return a != b
def _score(my_cat: dict, other: dict) -> dict:
score = 0
# 1. Same breed
if (my_cat.get("breed") or "") == (other.get("breed") or ""):
score += 30
# 2. Age similarity
try:
age_diff = abs(int(my_cat.get("age", 0)) - int(other.get("age", 0)))
score += max(0, 25 - min(25, age_diff * 5))
except Exception:
pass
# 3. Vaccination overlap
my_v = set(my_cat.get("vaccinations") or [])
ot_v = set(other.get("vaccinations") or [])
score += min(10, len(my_v.intersection(ot_v)) * 2)
# 4. Health similarity
if (my_cat.get("health") or "").lower() == (other.get("health") or "").lower():
score += 10
return {
"cat": other,
"score": int(score),
}
# ----------------------------
# Prompt builder (Poe analyze)
# ----------------------------
def _build_prompt(my_cat: dict, top_matches: list) -> str:
my_profile = my_cat.get("profile") or {}
my_summary = {
"name": my_cat.get("name"),
"breed": my_cat.get("breed"),
"age": my_cat.get("age"),
"sex_status": my_cat.get("gender"),
"health": my_cat.get("health"),
"vaccinations": my_cat.get("vaccinations"),
"source": my_profile.get("source"),
"owner_experience_years": my_profile.get("experience_years"),
"origin_knowledge": my_profile.get("origin_knowledge"),
}
candidates = []
for item in top_matches:
c = item.get("cat") or {}
candidates.append({
"name": c.get("name"),
"breed": c.get("breed"),
"age": c.get("age"),
"sex_status": c.get("gender"),
"health": c.get("health"),
"vaccinations": c.get("vaccinations"),
"score": item.get("score"),
})
return f"""
# Role:
Professional AI Feline Health & Breed Consultant.
# Context:
You are an AI consultant for cat breeding & care.
# User's Cat:
{json.dumps(my_summary, ensure_ascii=False, indent=2)}
# Potential Match Cats:
{json.dumps(candidates, ensure_ascii=False, indent=2)}
# Output Format (JSON ONLY, no markdown block, no extra text):
# Write summary_md in English only.
{{
"summary_md": "≤100 English words, markdown allowed. Brief compatibility summary in English.",
"image_prompt": "≤100 English words, for image generation"
}}
""".strip()
# ----------------------------
# API: GET /api/match-result
# ----------------------------
@bp.get("/match-result")
def match_result():
user = require_jwt()
user_id = int(user["user_id"])
email = user["email"]
store = Storage(current_app.config["SQLITE_PATH"])
my_cat = store.get_cat_by_user_id(user_id)
if not my_cat:
return jsonify({
"status": "error",
"message": "No cat profile"
}), 404
# -------- 配對計分 --------
others = store.list_other_cats(user_id=user_id, limit=200)
# Only consider cats with a different gender than the user's feline
others = [c for c in others if _different_gender(my_cat.get("gender"), c.get("gender"))]
scored = sorted(
[_score(my_cat, c) for c in others],
key=lambda x: x["score"],
reverse=True
)
top_matches = scored[:5]
# -------- Poe 文字分析 --------
review_md = "AI analysis is temporarily unavailable"
image_prompt = None
try:
logger.info("Building prompt for Poe analysis...")
prompt = _build_prompt(my_cat, top_matches)
logger.info(f"Prompt built successfully, length: {len(prompt)}")
logger.info("Calling analyze_with_poe...")
ai_result = analyze_with_poe(prompt)
logger.info(f"Poe analyze result: {ai_result}")
if isinstance(ai_result, dict):
review_md = ai_result.get("summary_md", review_md)
image_prompt = ai_result.get("image_prompt")
logger.info(f"Successfully extracted review_md and image_prompt")
else:
logger.warning(f"AI result is not a dict, got: {type(ai_result)}")
except Exception as e:
logger.exception(f"Poe analyze failed: {str(e)}")
current_app.logger.exception("Poe analyze failed")
# -------- OpenRouter 產圖 --------
kitten_image_url = None
image_result = None
if image_prompt:
image_prompt = image_prompt.strip()
# 強制 ≤100 字
if len(image_prompt) > 100:
image_prompt = image_prompt[:100]
try:
logger.info(f"Generating image with prompt: {image_prompt[:50]}...")
image_result = generate_kitten_image(
image_prompt,
logger=current_app.logger,
upload_dir=current_app.config["UPLOAD_DIR"],
)
logger.info(f"Image generation result: {image_result}")
except Exception as e:
logger.exception(f"Image generation crashed: {str(e)}")
current_app.logger.exception("Image generation crashed")
else:
logger.info("No image prompt provided, skipping image generation")
if image_result and image_result.get("success"):
kitten_image_url = image_result.get("image_url")
else:
if image_result:
current_app.logger.warning(
f"Image generation failed: {image_result.get('error')}"
)
# -------- Response --------
return jsonify({
"status": "ok",
"user": {
"email": email,
"user_id": user_id
},
"your_feline": my_cat,
"match_review_md": review_md,
"kitten_image_url": kitten_image_url,
"potential_matches": top_matches
}), 200
# ----------------------------
# Prompt builder for offspring generation
# ----------------------------
def _build_offspring_prompt(my_cat: dict, match_cat: dict) -> str:
"""Build a prompt for generating offspring review and image for a specific cat pair."""
my_profile = my_cat.get("profile") or {}
match_profile = match_cat.get("profile") or {}
my_summary = {
"name": my_cat.get("name"),
"breed": my_cat.get("breed"),
"age": my_cat.get("age"),
"sex_status": my_cat.get("gender"),
"health": my_cat.get("health"),
"vaccinations": my_cat.get("vaccinations"),
}
match_summary = {
"name": match_cat.get("name"),
"breed": match_cat.get("breed"),
"age": match_cat.get("age"),
"sex_status": match_cat.get("gender"),
"health": match_cat.get("health"),
"vaccinations": match_cat.get("vaccinations"),
}
return f"""
# Role:
Professional AI Feline Genetics & Breeding Consultant.
# Context:
You are analyzing the potential offspring of two specific cats. Provide insights about what their kitten might look like, inherit traits, and health considerations.
# Parent Cat 1 (User's Cat):
{json.dumps(my_summary, ensure_ascii=False, indent=2)}
# Parent Cat 2 (Selected Match):
{json.dumps(match_summary, ensure_ascii=False, indent=2)}
# Task:
Analyze the potential offspring of these two cats. Consider:
1. Likely breed characteristics and appearance
2. Potential health traits inherited from parents
3. Expected temperament based on parent breeds
# Output Format (JSON ONLY, no markdown block, no extra text):
# Write summary_md in English only.
{{
"summary_md": "≤200 English words, markdown allowed. Analysis of the potential offspring in English: appearance, health predictions, and care recommendations.",
"image_prompt": "≤200 English words describing a realistic cute kitten that would result from breeding a {my_cat.get('breed', 'cat')} with a {match_cat.get('breed', 'cat')}, include specific appearance details like fur color, pattern, eye color, and body type"
}}
""".strip()
# ----------------------------
# API: POST /api/generate-offspring
# ----------------------------
@bp.post("/generate-offspring")
def generate_offspring():
"""Generate AI review and kitten image for a specific cat pair."""
user = require_jwt()
user_id = int(user["user_id"])
data = request.get_json(silent=True) or {}
match_cat_id = data.get("match_cat_id")
if not match_cat_id:
return jsonify({
"status": "error",
"message": "match_cat_id is required"
}), 400
store = Storage(current_app.config["SQLITE_PATH"])
# Get user's cat
my_cat = store.get_cat_by_user_id(user_id)
if not my_cat:
return jsonify({
"status": "error",
"message": "No cat profile found"
}), 404
# Get the matched cat by ID
match_cat = None
others = store.list_other_cats(user_id=user_id, limit=200)
for cat in others:
if cat.get("id") == match_cat_id:
match_cat = cat
break
if not match_cat:
return jsonify({
"status": "error",
"message": "Match cat not found"
}), 404
if not _different_gender(my_cat.get("gender"), match_cat.get("gender")):
return jsonify({
"status": "error",
"message": "Your Feline and Potential Match must have different genders"
}), 400
# -------- Poe AI Analysis for this pair --------
review_md = "AI analysis is temporarily unavailable"
image_prompt = None
try:
logger.info(f"Building offspring prompt for cats: {my_cat.get('name')} + {match_cat.get('name')}")
prompt = _build_offspring_prompt(my_cat, match_cat)
logger.info(f"Offspring prompt built, length: {len(prompt)}")
logger.info("Calling analyze_with_poe for offspring...")
ai_result = analyze_with_poe(prompt)
logger.info(f"Offspring Poe result: {ai_result}")
if isinstance(ai_result, dict):
review_md = ai_result.get("summary_md", review_md)
image_prompt = ai_result.get("image_prompt")
logger.info(f"Extracted offspring review and image_prompt")
else:
logger.warning(f"AI result is not a dict, got: {type(ai_result)}")
except Exception as e:
logger.exception(f"Offspring Poe analyze failed: {str(e)}")
current_app.logger.exception("Offspring Poe analyze failed")
# -------- OpenRouter Image Generation --------
kitten_image_url = None
image_result = None
if image_prompt:
image_prompt = image_prompt.strip()
# Limit to 100 words
words = image_prompt.split()
if len(words) > 100:
image_prompt = " ".join(words[:100])
try:
logger.info(f"Generating offspring image with prompt: {image_prompt[:80]}...")
image_result = generate_kitten_image(
image_prompt,
logger=current_app.logger,
upload_dir=current_app.config["UPLOAD_DIR"],
)
logger.info(f"Offspring image generation result: {image_result}")
except Exception as e:
logger.exception(f"Offspring image generation crashed: {str(e)}")
current_app.logger.exception("Offspring image generation crashed")
else:
logger.info("No image prompt for offspring, skipping image generation")
if image_result and image_result.get("success"):
kitten_image_url = image_result.get("image_url")
else:
if image_result:
current_app.logger.warning(
f"Offspring image generation failed: {image_result.get('error')}"
)
# -------- Response --------
return jsonify({
"status": "ok",
"match_cat_id": match_cat_id,
"match_cat_name": match_cat.get("name"),
"review_md": review_md,
"kitten_image_url": kitten_image_url
}), 200
Articles liés
__init__.py
__init__.py — python source code from the Ricky learning materials (Ricky/Cat Project Final/back_web-4/app/__init__.py).
Lire l'article →auth.py
auth.py — python source code from the Ricky learning materials (Ricky/Cat Project Final/back_web-4/app/admin/auth.py).
Lire l'article →routes.py
routes.py — python source code from the Ricky learning materials (Ricky/Cat Project Final/back_web-4/app/admin/routes.py).
Lire l'article →config.py
config.py — python source code from the Ricky learning materials (Ricky/Cat Project Final/back_web-4/app/config.py).
Lire l'article →admin_ingest.py
admin_ingest.py — python source code from the Ricky learning materials (Ricky/Cat Project Final/back_web-4/app/routes/admin_ingest.py).
Lire l'article →admin_stats.py
admin_stats.py — python source code from the Ricky learning materials (Ricky/Cat Project Final/back_web-4/app/routes/admin_stats.py).
Lire l'article →