added strtegy import and amployee strategy

This commit is contained in:
2026-03-16 11:21:43 +01:00
parent a3ea774b58
commit 46793f4acf
12 changed files with 1200 additions and 415 deletions

View File

@@ -44,7 +44,9 @@ from src.services.storage_service import storage
from src.services.link_extractor import LinkExtractor, LinkExtractionError
from src.services.file_extractor import FileExtractor, FileExtractionError
from src.agents.link_topic_builder import LinkTopicBuilderAgent
from src.agents.strategy_importer import StrategyImporterAgent
from src.services.post_insights_service import compute_post_insights, refresh_post_insights_for_account
from src.services.insights_summary_service import generate_insights_summary
# Router for user frontend
user_router = APIRouter(tags=["user"])
@@ -3036,14 +3038,34 @@ async def refresh_post_insights(request: Request):
metadata = profile.metadata or {}
today = datetime.now(timezone.utc).date().isoformat()
last_refresh = metadata.get("post_insights_manual_refresh_date")
if last_refresh == today:
if settings.insights_manual_refresh_limit_enabled and last_refresh == today:
raise HTTPException(status_code=429, detail="Manual refresh already used today")
await refresh_post_insights_for_account(db, linkedin_account)
# Mark refresh immediately to avoid concurrent spam
metadata["post_insights_manual_refresh_date"] = today
await db.update_profile(user_id, {"metadata": metadata})
return {"success": True, "refreshed_at": today}
async def _bg_refresh_and_summary():
try:
await refresh_post_insights_for_account(db, linkedin_account)
from datetime import date, timedelta
since = (date.today() - timedelta(days=90)).isoformat()
insights_posts = await db.get_post_insights_posts(user_id)
insights_daily = await db.get_post_insights_daily(user_id, since_date=since)
post_insights = compute_post_insights(insights_posts, insights_daily)
if post_insights and post_insights.get("has_data"):
summary = await asyncio.to_thread(generate_insights_summary, post_insights)
if summary:
metadata["post_insights_summary"] = summary
metadata["post_insights_summary_date"] = today
await db.update_profile(user_id, {"metadata": metadata})
except Exception as e:
logger.warning(f"Manual insights refresh failed: {e}")
asyncio.create_task(_bg_refresh_and_summary())
return {"success": True, "refreshed_at": today, "queued": True}
except HTTPException:
raise
@@ -3122,6 +3144,63 @@ async def company_strategy_submit(request: Request):
})
@user_router.post("/company/strategy/import")
async def company_strategy_import(request: Request):
"""Import company strategy from a PDF document."""
session = require_user_session(request)
if not session:
raise HTTPException(status_code=401, detail="Not authenticated")
if session.account_type != "company" or not session.company_id:
raise HTTPException(status_code=403, detail="Company account required")
# Check token limit for companies/employees
can_create, error_msg, _, _ = await db.check_company_token_limit(UUID(session.company_id))
if not can_create:
raise HTTPException(status_code=429, detail=error_msg)
try:
form = await request.form()
upload: UploadFile = form.get("file") # type: ignore[assignment]
if not upload:
raise HTTPException(status_code=400, detail="Keine Datei hochgeladen.")
filename = upload.filename or ""
ext = Path(filename).suffix.lower()
if ext != ".pdf":
raise HTTPException(status_code=400, detail="Bitte eine PDF-Datei hochladen.")
file_bytes = await upload.read()
max_bytes = 10 * 1024 * 1024 # 10 MB
if len(file_bytes) > max_bytes:
raise HTTPException(status_code=400, detail="Datei ist zu groß (max 10 MB).")
extractor = FileExtractor()
try:
text = extractor.extract_text(file_bytes, filename)
except FileExtractionError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
if len(text) > 50000:
text = text[:50000]
company = await db.get_company(UUID(session.company_id))
importer = StrategyImporterAgent()
importer.set_tracking_context(
operation="company_strategy_import",
user_id=session.user_id,
company_id=session.company_id
)
strategy = await importer.process(text, company_name=company.name if company else None)
return JSONResponse({"success": True, "strategy": strategy})
except HTTPException:
raise
except Exception as e:
logger.exception(f"Company strategy import failed: {e}")
raise HTTPException(status_code=500, detail=str(e))
@user_router.get("/company/accounts", response_class=HTMLResponse)
async def company_accounts_page(request: Request):
"""Company employee management page."""
@@ -3589,8 +3668,8 @@ async def company_manage_post_types(request: Request, employee_id: str = None):
# ==================== EMPLOYEE ROUTES ====================
@user_router.get("/employee/strategy", response_class=HTMLResponse)
async def employee_strategy_page(request: Request):
"""Read-only company strategy view for employees."""
async def employee_strategy_page(request: Request, success: bool = False):
"""Employee profile analysis (strategy) view."""
session = require_user_session(request)
if not session:
return RedirectResponse(url="/login", status_code=302)
@@ -3599,63 +3678,70 @@ async def employee_strategy_page(request: Request):
if session.account_type != "employee" or not session.company_id:
return RedirectResponse(url="/", status_code=302)
company = await db.get_company(UUID(session.company_id))
strategy = company.company_strategy if company else {}
user_id = UUID(session.user_id)
profile_picture = await get_user_avatar(session, user_id)
profile_analysis = await db.get_profile_analysis(user_id)
analysis = profile_analysis.full_analysis if profile_analysis else {}
if not isinstance(analysis, dict):
analysis = {}
analysis_json = json.dumps(analysis, ensure_ascii=False, indent=2)
return templates.TemplateResponse("employee_strategy.html", {
"request": request,
"page": "strategy",
"session": session,
"company": company,
"strategy": strategy,
"profile_analysis": analysis,
"analysis_json": analysis_json,
"analysis_created_at": profile_analysis.created_at if profile_analysis else None,
"success": success,
"profile_picture": profile_picture
})
@user_router.get("/employee/insights", response_class=HTMLResponse)
async def employee_insights_page(request: Request):
"""Employee post insights page."""
@user_router.post("/employee/strategy", response_class=HTMLResponse)
async def employee_strategy_submit(request: Request):
"""Save edited profile analysis for employees."""
session = require_user_session(request)
if not session:
return RedirectResponse(url="/login", status_code=302)
if session.account_type != "employee":
if session.account_type != "employee" or not session.company_id:
return RedirectResponse(url="/", status_code=302)
form = await request.form()
raw_json = (form.get("analysis_json") or "").strip()
try:
analysis = json.loads(raw_json) if raw_json else {}
if not isinstance(analysis, dict):
raise ValueError("JSON muss ein Objekt sein.")
from src.database.models import ProfileAnalysis
user_id = UUID(session.user_id)
analysis_record = ProfileAnalysis(
user_id=user_id,
writing_style=analysis.get("writing_style", {}) or {},
tone_analysis=analysis.get("tone_analysis", {}) or {},
topic_patterns=analysis.get("topic_patterns", {}) or {},
audience_insights=analysis.get("audience_insights", {}) or {},
full_analysis=analysis
)
await db.save_profile_analysis(analysis_record)
return RedirectResponse(url="/employee/strategy?success=true", status_code=302)
except Exception as e:
user_id = UUID(session.user_id)
profile_picture = await get_user_avatar(session, user_id)
linkedin_account = await db.get_linkedin_account(user_id)
post_insights = {"has_data": False}
if linkedin_account:
try:
from datetime import date, timedelta
since = (date.today() - timedelta(days=90)).isoformat()
insights_posts = await db.get_post_insights_posts(user_id)
insights_daily = await db.get_post_insights_daily(user_id, since_date=since)
post_insights = compute_post_insights(insights_posts, insights_daily)
except Exception as e:
logger.error(f"Error computing post insights: {e}")
return templates.TemplateResponse("employee_insights.html", {
return templates.TemplateResponse("employee_strategy.html", {
"request": request,
"page": "insights",
"page": "strategy",
"session": session,
"profile_picture": profile_picture,
"linkedin_account": linkedin_account,
"post_insights": post_insights
})
except Exception as e:
logger.error(f"Error loading insights: {e}")
return templates.TemplateResponse("employee_insights.html", {
"request": request,
"page": "insights",
"session": session,
"error": str(e)
"profile_analysis": {},
"analysis_json": raw_json,
"analysis_created_at": None,
"error": f"Fehler beim Speichern: {e}",
"profile_picture": profile_picture
})