diff --git a/config/migrate_add_strategy_weight.sql b/config/migrate_add_strategy_weight.sql new file mode 100644 index 0000000..a4354e1 --- /dev/null +++ b/config/migrate_add_strategy_weight.sql @@ -0,0 +1,10 @@ +-- Add strategy_weight column to post_types table +-- This column controls how strongly the company strategy influences AI post generation +-- Range: 0.0 (ignore strategy) to 1.0 (strictly follow strategy) + +ALTER TABLE post_types +ADD COLUMN IF NOT EXISTS strategy_weight FLOAT DEFAULT 0.5 +CHECK (strategy_weight >= 0.0 AND strategy_weight <= 1.0); + +-- Set default value for existing rows +UPDATE post_types SET strategy_weight = 0.5 WHERE strategy_weight IS NULL; diff --git a/src/agents/post_classifier.py b/src/agents/post_classifier.py index 0665936..2f2cabf 100644 --- a/src/agents/post_classifier.py +++ b/src/agents/post_classifier.py @@ -184,8 +184,10 @@ class PostClassifierAgent(BaseAgent): system_prompt = """Du bist ein Content-Analyst, der LinkedIn-Posts in vordefinierte Kategorien einordnet. +WICHTIG: JEDER Post MUSS kategorisiert werden. Du MUSST für jeden Post den am besten passenden Post-Typ wählen, auch wenn die Übereinstimmung nicht perfekt ist. + Analysiere jeden Post und ordne ihn dem passendsten Post-Typ zu. -Wenn kein Typ wirklich passt, gib "null" als post_type_id zurück. +Es ist NICHT erlaubt, "null" als post_type_id zurückzugeben. Bewerte die Zuordnung mit einer Confidence zwischen 0.3 und 1.0: - 0.9-1.0: Sehr sicher, Post passt perfekt zum Typ @@ -209,12 +211,14 @@ Gib ein JSON-Objekt zurück mit diesem Format: "classifications": [ {{ "post_id": "uuid-des-posts", - "post_type_id": "uuid-des-typs oder null", + "post_type_id": "uuid-des-typs (IMMER erforderlich, niemals null)", "confidence": 0.8, "reasoning": "Kurze Begründung" }} ] -}}""" +}} + +WICHTIG: Jeder Post MUSS einen post_type_id bekommen. Wähle den am besten passenden Typ, auch wenn die Übereinstimmung nicht perfekt ist.""" try: response = await self.call_openai( @@ -230,6 +234,8 @@ Gib ein JSON-Objekt zurück mit diesem Format: # Process and validate results valid_results = [] + unclassified_posts = list(posts) # Track which posts still need classification + for c in classifications: post_id = c.get("post_id") post_type_id = c.get("post_type_id") @@ -244,7 +250,8 @@ Gib ein JSON-Objekt zurück mit diesem Format: # Validate post_type_id if post_type_id and post_type_id != "null" and post_type_id not in valid_type_ids: logger.warning(f"Invalid post_type_id in classification: {post_type_id}") - continue + # Fallback: assign to first available type + post_type_id = list(valid_type_ids - {"null"})[0] if valid_type_ids - {"null"} else None if post_type_id and post_type_id != "null": valid_results.append({ @@ -253,6 +260,19 @@ Gib ein JSON-Objekt zurück mit diesem Format: "classification_method": "semantic", "classification_confidence": min(1.0, max(0.3, confidence)) }) + unclassified_posts = [p for p in unclassified_posts if p.id != matching_post.id] + + # FORCE CLASSIFY remaining unclassified posts + if unclassified_posts and post_types: + default_type = post_types[0] # Use first type as fallback + logger.warning(f"Force-classifying {len(unclassified_posts)} posts to default type: {default_type.name}") + for post in unclassified_posts: + valid_results.append({ + "post_id": post.id, + "post_type_id": default_type.id, + "classification_method": "semantic_fallback", + "classification_confidence": 0.3 + }) return valid_results diff --git a/src/agents/researcher.py b/src/agents/researcher.py index 2c6dd51..e6001f6 100644 --- a/src/agents/researcher.py +++ b/src/agents/researcher.py @@ -22,7 +22,9 @@ class ResearchAgent(BaseAgent): customer_data: Dict[str, Any], example_posts: List[str] = None, post_type: Any = None, - post_type_analysis: Dict[str, Any] = None + post_type_analysis: Dict[str, Any] = None, + company_strategy: Dict[str, Any] = None, + strategy_weight: float = 0.5 ) -> Dict[str, Any]: """ Research new content topics. @@ -34,6 +36,8 @@ class ResearchAgent(BaseAgent): example_posts: List of the person's actual posts for style reference post_type: Optional PostType object for targeted research post_type_analysis: Optional post type analysis for context + company_strategy: Optional company strategy to align topics with + strategy_weight: Strategy weight (0.0-1.0) controlling strategy influence Returns: Research results with suggested topics @@ -90,7 +94,9 @@ class ResearchAgent(BaseAgent): example_posts=example_posts or [], existing_topics=existing_topics, post_type=post_type, - post_type_analysis=post_type_analysis + post_type_analysis=post_type_analysis, + company_strategy=company_strategy, + strategy_weight=strategy_weight ) response = await self.call_openai( @@ -379,7 +385,9 @@ QUALITÄTSKRITERIEN: example_posts: List[str], existing_topics: List[str], post_type: Any = None, - post_type_analysis: Dict[str, Any] = None + post_type_analysis: Dict[str, Any] = None, + company_strategy: Dict[str, Any] = None, + strategy_weight: float = 0.5 ) -> str: """Transform raw research into personalized, concrete topic suggestions.""" @@ -428,8 +436,11 @@ QUALITÄTSKRITERIEN: post_type_section += "\n**WICHTIG:** Alle Themenvorschläge müssen zu diesem Post-Typ passen!\n" + # Build company strategy section + strategy_section = self._get_strategy_section(company_strategy, strategy_weight) + return f"""AUFGABE: Transformiere die Recherche-Ergebnisse in KONKRETE, PERSONALISIERTE Themenvorschläge. -{post_type_section} +{post_type_section}{strategy_section} === RECHERCHE-ERGEBNISSE (Rohdaten) === {raw_research} @@ -492,6 +503,71 @@ WICHTIG: - Hook-Ideen müssen zum Stil der Beispiel-Posts passen! - Key Facts müssen aus der Recherche stammen (keine erfundenen Zahlen)""" + def _get_strategy_section(self, company_strategy: Dict[str, Any] = None, strategy_weight: float = 0.5) -> str: + """Build company strategy section for research prompt with weighted influence.""" + if not company_strategy: + return "" + + # Extract strategy elements + mission = company_strategy.get("mission", "") + vision = company_strategy.get("vision", "") + content_pillars = company_strategy.get("content_pillars", []) + target_audience = company_strategy.get("target_audience", "") + dos = company_strategy.get("dos", []) + donts = company_strategy.get("donts", []) + + # Build section only if there's meaningful content + if not any([mission, vision, content_pillars, dos, donts]): + return "" + + # Determine importance level based on strategy_weight + if strategy_weight >= 0.8: + importance = "KRITISCH - STRIKT BEACHTEN" + instruction = "Alle Themen MÜSSEN sich an der Unternehmensstrategie orientieren! Nur Topics vorschlagen, die perfekt zur Strategie passen." + elif strategy_weight >= 0.6: + importance = "WICHTIG - BERÜCKSICHTIGEN" + instruction = "Priorisiere Topics die zur Unternehmensstrategie passen! Die Strategie soll klar erkennbar sein." + elif strategy_weight >= 0.4: + importance = "ORIENTIERUNG" + instruction = "Berücksichtige die Unternehmensstrategie wo sinnvoll - persönliche Expertise hat Priorität." + elif strategy_weight >= 0.2: + importance = "OPTIONAL" + instruction = "Die Strategie dient als Hintergrundinformation - freie Themenauswahl erlaubt." + else: + importance = "NUR REFERENZ" + instruction = "Ignoriere die Strategie weitgehend - Fokus auf persönliche Themen und Expertise." + + section = f""" + +=== UNTERNEHMENSSTRATEGIE ({importance}) === +Strategy Weight: {strategy_weight:.1f} / 1.0 +{instruction} +""" + if mission: + section += f"\nMISSION: {mission}" + if vision: + section += f"\nVISION: {vision}" + if content_pillars: + section += f"\nCONTENT PILLARS: {', '.join(content_pillars)}" + if target_audience: + section += f"\nZIELGRUPPE: {target_audience}" + if dos: + section += f"\n\nEMPFOHLENE THEMEN-RICHTUNGEN:\n" + "\n".join([f" + {d}" for d in dos]) + if donts: + section += f"\n\nZU VERMEIDENDE THEMEN:\n" + "\n".join([f" - {d}" for d in donts]) + + # Weight-specific closing instruction + if strategy_weight >= 0.8: + section += "\n\n**KRITISCH:** Alle Topics müssen zur Unternehmensstrategie passen - keine Ausnahmen!" + elif strategy_weight >= 0.6: + section += "\n\n**WICHTIG:** Stelle sicher, dass die Mehrheit der Topics die Unternehmenswerte widerspiegelt!" + elif strategy_weight >= 0.4: + section += "\n\n**BEACHTE:** Integriere die Strategie subtil - persönliche Expertise bleibt wichtig!" + else: + section += "\n\n**REFERENZ:** Die Strategie dient nur als Orientierung - persönliche Topics haben Vorrang!" + + return section + "\n" + def _get_structure_prompt( self, raw_research: str, diff --git a/src/agents/writer.py b/src/agents/writer.py index 9fe0369..742bc76 100644 --- a/src/agents/writer.py +++ b/src/agents/writer.py @@ -30,7 +30,8 @@ class WriterAgent(BaseAgent): post_type_analysis: Optional[Dict[str, Any]] = None, user_thoughts: str = "", selected_hook: str = "", - company_strategy: Optional[Dict[str, Any]] = None + company_strategy: Optional[Dict[str, Any]] = None, + strategy_weight: float = 0.5 ) -> str: """ Write a LinkedIn post. @@ -46,6 +47,7 @@ class WriterAgent(BaseAgent): post_type: Optional PostType object for type-specific writing post_type_analysis: Optional analysis of the post type company_strategy: Optional company strategy to consider during writing + strategy_weight: Strategy weight (0.0-1.0) controlling how strongly the company strategy influences post generation Returns: Written LinkedIn post @@ -65,7 +67,8 @@ class WriterAgent(BaseAgent): post_type_analysis=post_type_analysis, user_thoughts=user_thoughts, selected_hook=selected_hook, - company_strategy=company_strategy + company_strategy=company_strategy, + strategy_weight=strategy_weight ) else: logger.info(f"Writing initial post for topic: {topic.get('title', 'Unknown')}") @@ -90,7 +93,8 @@ class WriterAgent(BaseAgent): post_type_analysis=post_type_analysis, user_thoughts=user_thoughts, selected_hook=selected_hook, - company_strategy=company_strategy + company_strategy=company_strategy, + strategy_weight=strategy_weight ) else: return await self._write_single_draft( @@ -102,7 +106,8 @@ class WriterAgent(BaseAgent): post_type_analysis=post_type_analysis, user_thoughts=user_thoughts, selected_hook=selected_hook, - company_strategy=company_strategy + company_strategy=company_strategy, + strategy_weight=strategy_weight ) def _select_example_posts( @@ -236,7 +241,8 @@ class WriterAgent(BaseAgent): post_type_analysis: Optional[Dict[str, Any]] = None, user_thoughts: str = "", selected_hook: str = "", - company_strategy: Optional[Dict[str, Any]] = None + company_strategy: Optional[Dict[str, Any]] = None, + strategy_weight: float = 0.5 ) -> str: """ Generate multiple drafts and select the best one. @@ -249,6 +255,7 @@ class WriterAgent(BaseAgent): post_type: Optional PostType object post_type_analysis: Optional post type analysis company_strategy: Optional company strategy to consider + strategy_weight: Strategy weight (0.0-1.0) for company strategy influence Returns: Best selected draft @@ -256,7 +263,7 @@ class WriterAgent(BaseAgent): num_drafts = min(max(settings.writer_multi_draft_count, 2), 5) # Clamp between 2-5 logger.info(f"Generating {num_drafts} drafts for selection") - system_prompt = self._get_system_prompt(profile_analysis, example_posts, learned_lessons, post_type, post_type_analysis, company_strategy) + system_prompt = self._get_system_prompt(profile_analysis, example_posts, learned_lessons, post_type, post_type_analysis, company_strategy, strategy_weight) # Generate drafts in parallel with different temperatures/approaches draft_configs = [ @@ -500,7 +507,8 @@ Analysiere jeden Entwurf kurz und wähle den besten. Antworte im JSON-Format: post_type_analysis: Optional[Dict[str, Any]] = None, user_thoughts: str = "", selected_hook: str = "", - company_strategy: Optional[Dict[str, Any]] = None + company_strategy: Optional[Dict[str, Any]] = None, + strategy_weight: float = 0.5 ) -> str: """Write a single draft (original behavior).""" # Select examples if not already selected @@ -515,7 +523,7 @@ Analysiere jeden Entwurf kurz und wähle den besten. Antworte im JSON-Format: elif len(selected_examples) > 3: selected_examples = random.sample(selected_examples, 3) - system_prompt = self._get_system_prompt(profile_analysis, selected_examples, learned_lessons, post_type, post_type_analysis, company_strategy) + system_prompt = self._get_system_prompt(profile_analysis, selected_examples, learned_lessons, post_type, post_type_analysis, company_strategy, strategy_weight) user_prompt = self._get_user_prompt(topic, feedback, previous_version, critic_result, user_thoughts, selected_hook) # OPTIMIERT: Niedrigere Temperature (0.5 statt 0.6) für konsistenteren Stil @@ -536,7 +544,8 @@ Analysiere jeden Entwurf kurz und wähle den besten. Antworte im JSON-Format: learned_lessons: Optional[Dict[str, Any]] = None, post_type: Any = None, post_type_analysis: Optional[Dict[str, Any]] = None, - company_strategy: Optional[Dict[str, Any]] = None + company_strategy: Optional[Dict[str, Any]] = None, + strategy_weight: float = 0.5 ) -> str: """Get system prompt for writer - orientiert an bewährten n8n-Prompts.""" # Extract key profile information @@ -788,13 +797,13 @@ Vermeide IMMER diese KI-typischen Muster: - Zu perfekte, glatte Formulierungen - echte Menschen schreiben mit Ecken und Kanten {lessons_section} {post_type_section} -{self._get_company_strategy_section(company_strategy)} +{self._get_company_strategy_section(company_strategy, strategy_weight)} DEIN AUFTRAG: Schreibe den Post so, dass er für die Zielgruppe ({audience.get('target_audience', 'Professionals')}) einen klaren Mehrwert bietet und ihre Pain Points ({pain_points_str}) adressiert. Mach die Persönlichkeit des linguistischen Fingerabdrucks spürbar. Beginne DIREKT mit dem Hook. Keine einleitenden Sätze, kein "Hier ist der Post".""" - def _get_company_strategy_section(self, company_strategy: Optional[Dict[str, Any]] = None) -> str: - """Build the company strategy section for the system prompt.""" + def _get_company_strategy_section(self, company_strategy: Optional[Dict[str, Any]] = None, strategy_weight: float = 0.5) -> str: + """Build the company strategy section for the system prompt with weighted influence.""" if not company_strategy: return "" @@ -812,11 +821,29 @@ Beginne DIREKT mit dem Hook. Keine einleitenden Sätze, kein "Hier ist der Post" if not any([mission, vision, brand_voice, content_pillars, dos, donts]): return "" - section = """ + # Determine importance level and instruction based on strategy_weight + if strategy_weight >= 0.8: + importance = "KRITISCH WICHTIG - STRIKT BEFOLGEN" + instruction = "Der Post MUSS sich eng an die Unternehmensstrategie halten! Die Strategy-Guidelines haben höchste Priorität." + elif strategy_weight >= 0.6: + importance = "WICHTIG - BEACHTEN" + instruction = "Integriere die Unternehmenswerte deutlich, aber bewahre Authentizität! Die Strategie soll klar erkennbar sein." + elif strategy_weight >= 0.4: + importance = "ALS KONTEXT BERÜCKSICHTIGEN" + instruction = "Berücksichtige die Strategie wo sinnvoll - Persönlicher Stil hat Priorität! Subtile Integration genügt." + elif strategy_weight >= 0.2: + importance = "OPTIONALE ORIENTIERUNG" + instruction = "Die Strategie dient als Hintergrundinformation - freie Interpretation erlaubt! Fokus auf Authentizität." + else: + importance = "NUR ALS REFERENZ" + instruction = "Ignoriere die Strategie weitgehend - voller Fokus auf persönlichen Stil! Strategy-Guidelines sind optional." -8. UNTERNEHMENSSTRATEGIE (WICHTIG - IMMER BEACHTEN!): + section = f""" -Der Post muss zur Unternehmensstrategie passen, aber authentisch im Stil des Autors bleiben! +8. UNTERNEHMENSSTRATEGIE ({importance}): + +Strategy Weight: {strategy_weight:.1f} / 1.0 +{instruction} """ if mission: section += f"\nMISSION: {mission}" @@ -835,7 +862,15 @@ Der Post muss zur Unternehmensstrategie passen, aber authentisch im Stil des Aut if donts: section += f"\n\nDON'Ts (vermeiden):\n" + "\n".join([f" - {d}" for d in donts]) - section += "\n\nWICHTIG: Integriere die Unternehmenswerte subtil - der Post soll nicht wie eine Werbung klingen!" + # Weight-specific closing instruction + if strategy_weight >= 0.8: + section += "\n\nKRITISCH: Die Unternehmensstrategie ist nicht verhandelbar - jeder Post muss diese widerspiegeln!" + elif strategy_weight >= 0.6: + section += "\n\nWICHTIG: Stelle sicher, dass die Unternehmenswerte deutlich erkennbar sind!" + elif strategy_weight >= 0.4: + section += "\n\nINTEGRIERE: Berücksichtige die Strategie subtil - authentischer Stil bleibt wichtig!" + else: + section += "\n\nREFERENZ: Die Strategie dient nur als Orientierung - persönlicher Stil hat Vorrang!" return section diff --git a/src/database/models.py b/src/database/models.py index 2df83a1..013eaf5 100644 --- a/src/database/models.py +++ b/src/database/models.py @@ -226,6 +226,7 @@ class PostType(DBModel): analysis_generated_at: Optional[datetime] = None analyzed_post_count: int = 0 is_active: bool = True + strategy_weight: float = 0.5 class LinkedInProfile(DBModel): diff --git a/src/orchestrator.py b/src/orchestrator.py index 6ff4fd4..92ea072 100644 --- a/src/orchestrator.py +++ b/src/orchestrator.py @@ -342,11 +342,13 @@ class WorkflowOrchestrator: # Get post type context if specified post_type = None post_type_analysis = None + strategy_weight = 0.5 # Default strategy weight if post_type_id: post_type = await db.get_post_type(post_type_id) if post_type: post_type_analysis = post_type.analysis - logger.info(f"Targeting research for post type: {post_type.name}") + strategy_weight = post_type.strategy_weight + logger.info(f"Targeting research for post type: {post_type.name} with strategy weight {strategy_weight:.1f}") def report_progress(message: str, step: int, total: int = 4): if progress_callback: @@ -358,6 +360,15 @@ class WorkflowOrchestrator: if not profile_analysis: raise ValueError("Profile analysis not found. Run initial setup first.") + # Step 1.5: Load company strategy if user belongs to a company + company_strategy = None + profile = await db.get_profile(user_id) + if profile and profile.company_id: + company = await db.get_company(profile.company_id) + if company and company.company_strategy: + company_strategy = company.company_strategy + logger.info(f"Loaded company strategy for research: {company.name}") + # Step 2: Get ALL existing topics (from multiple sources to avoid repetition) report_progress("Lade existierende Topics...", 2) existing_topics = set() @@ -384,9 +395,6 @@ class WorkflowOrchestrator: existing_topics = list(existing_topics) logger.info(f"Found {len(existing_topics)} existing topics to avoid") - # Get profile data - profile = await db.get_profile(user_id) - # Get example posts to understand the person's actual content style # If post_type_id is specified, only use posts of that type if post_type_id: @@ -409,7 +417,9 @@ class WorkflowOrchestrator: customer_data=profile.metadata, example_posts=example_post_texts, post_type=post_type, - post_type_analysis=post_type_analysis + post_type_analysis=post_type_analysis, + company_strategy=company_strategy, + strategy_weight=strategy_weight ) # Step 4: Save research results @@ -582,11 +592,14 @@ class WorkflowOrchestrator: # Get post type info if specified post_type = None post_type_analysis = None + strategy_weight = 0.5 # Default strategy weight if post_type_id: post_type = await db.get_post_type(post_type_id) - if post_type and post_type.analysis: - post_type_analysis = post_type.analysis - logger.info(f"Using post type '{post_type.name}' for writing") + if post_type: + if post_type.analysis: + post_type_analysis = post_type.analysis + strategy_weight = post_type.strategy_weight # Extract strategy weight from post type + logger.info(f"Using post type '{post_type.name}' with strategy weight {strategy_weight:.1f}") # Load user's real posts as style examples # If post_type_id is specified, only use posts of that type @@ -642,7 +655,8 @@ class WorkflowOrchestrator: post_type_analysis=post_type_analysis, user_thoughts=user_thoughts, selected_hook=selected_hook, - company_strategy=company_strategy # NEW: Pass company strategy + company_strategy=company_strategy, # Pass company strategy + strategy_weight=strategy_weight # NEW: Pass strategy weight ) else: # Revision based on feedback - pass full critic result for structured changes @@ -660,7 +674,8 @@ class WorkflowOrchestrator: post_type_analysis=post_type_analysis, user_thoughts=user_thoughts, selected_hook=selected_hook, - company_strategy=company_strategy # NEW: Pass company strategy + company_strategy=company_strategy, # Pass company strategy + strategy_weight=strategy_weight # NEW: Pass strategy weight ) writer_versions.append(current_post) diff --git a/src/services/background_jobs.py b/src/services/background_jobs.py index fa825ff..8abea2e 100644 --- a/src/services/background_jobs.py +++ b/src/services/background_jobs.py @@ -396,6 +396,101 @@ async def run_post_categorization(user_id: UUID, job_id: str): ) +async def run_post_recategorization(user_id: UUID, job_id: str): + """Re-categorize ALL LinkedIn posts (including already categorized ones).""" + from src.database.client import DatabaseClient + from src.agents.post_classifier import PostClassifierAgent + import asyncio + + db = DatabaseClient() + + try: + await job_manager.update_job( + job_id, + status=JobStatus.RUNNING, + progress=5, + message="Lade alle LinkedIn Posts..." + ) + + # Get ALL LinkedIn posts (not just unclassified) + posts = await db.get_linkedin_posts(user_id) + post_types = await db.get_post_types(user_id) + + if not posts: + await job_manager.update_job( + job_id, + status=JobStatus.COMPLETED, + progress=100, + message="Keine Posts gefunden!" + ) + return + + if not post_types: + await job_manager.update_job( + job_id, + status=JobStatus.FAILED, + error="Keine Post-Typen definiert" + ) + return + + # FIRST: Mark all posts as uncategorized before re-categorizing + await job_manager.update_job( + job_id, + progress=15, + message=f"Markiere {len(posts)} Posts als unkategorisiert..." + ) + + # Set post_type_id to NULL for all posts + for post in posts: + try: + await asyncio.to_thread( + lambda p=post: db.client.table("linkedin_posts").update({ + "post_type_id": None + }).eq("id", str(p.id)).execute() + ) + except Exception as e: + logger.warning(f"Failed to unclassify post {post.id}: {e}") + + logger.info(f"Marked {len(posts)} posts as uncategorized") + + await job_manager.update_job( + job_id, + progress=30, + message=f"Re-kategorisiere {len(posts)} Posts..." + ) + + # Run classification on ALL posts + classifier = PostClassifierAgent() + classifications = await classifier.process(posts, post_types) + + await job_manager.update_job( + job_id, + progress=70, + message="Speichere neue Kategorisierungen..." + ) + + # Save classifications + if classifications: + await db.update_posts_classification_bulk(classifications) + + await job_manager.update_job( + job_id, + status=JobStatus.COMPLETED, + progress=100, + message=f"{len(classifications)} Posts re-kategorisiert!" + ) + + logger.info(f"Post re-categorization completed for user {user_id}: {len(classifications)} posts") + + except Exception as e: + logger.error(f"Post re-categorization failed: {e}") + await job_manager.update_job( + job_id, + status=JobStatus.FAILED, + error=str(e) + ) + + async def run_post_type_analysis(user_id: UUID, job_id: str): """Run post type analysis in background.""" from src.database.client import DatabaseClient diff --git a/src/web/templates/user/company_accounts.html b/src/web/templates/user/company_accounts.html index 0a093be..63c3eb6 100644 --- a/src/web/templates/user/company_accounts.html +++ b/src/web/templates/user/company_accounts.html @@ -77,11 +77,11 @@ {% for employee in employees %}