feat: initial docs repo with project inventory and all documentation

- Added REPO_INVENTORY.md with all repos, branches, remotes, and staging info
- Added .gitignore
- Synced all existing docs from local workspace
- Centralized documentation hub for GrowQR team
This commit is contained in:
-Puter
2026-06-22 15:04:27 +05:30
commit e6685203fe
71 changed files with 35044 additions and 0 deletions
+275
View File
@@ -0,0 +1,275 @@
# Changelogs (main ➜ feature-update)
Date: 26 Mar 2026
Scope: Diff between `origin/main` and `feature-update`
## Summary
`feature-update` is a large feature + architecture update.
- **Scale of change:** 85 files changed, **+11,633 / -3,266** lines.
- **Primary outcomes:**
- Adds **QScore integration** and user-level caching.
- Adds a **Brand Score engine** (with audit output, quick wins, and optional eventing).
- Introduces a more explicit architecture: **Adapters → Use-cases → Repositories → Engine**.
- Improves worker/job processing with **job dispatching + idempotency**.
- Adds **demo/dev ergonomics**: no-auth mode, demo token, and `/demo` UI.
- Adds significant **test coverage** and **documentation**.
---
## What’s new (features added)
### 1) QScore integration
**Goal:** Fetch/compute QScore and persist a read-optimized snapshot for downstream UX and scoring.
**Key additions:**
- API module: `app/api/v1/qscore.py`
- Client: `app/clients/qscore_client.py`
- Service: `app/services/qscore_service.py`
- Schema types: `app/schemas/qscore.py`
- DB cache column via migration:
- `alembic/versions/20260325_000001_add_qscore_summary_to_social_users.py`
- Model update:
- `app/models/user.py` adds `qscore_summary` (JSONB)
**Net effect:** The service can expose QScore data and keep the latest snapshot in the user row for fast reads.
---
### 2) Brand Score engine (+ summaries)
**Goal:** Compute and explain a “Brand Score” with actionable guidance.
**Key additions:**
- Engine modules:
- `app/engine/brand_score_engine.py`
- `app/engine/audit_engine.py`
- `app/engine/persona_engine.py`
- `app/engine/draft_engine.py`
- `app/engine/calendar_engine.py`
- `app/engine/contracts.py`
- `app/engine/scoring_rules.py`
- Eventing:
- `app/services/brand_score_eventing.py`
- DB cache column via migration:
- `alembic/versions/20260325_000002_add_brand_score_summary_to_social_users.py`
- Model update:
- `app/models/user.py` adds `brand_score_summary` (JSONB)
**Behavioral highlights:**
- Brand score output includes structured “**strengths**” and top “**quick wins**” derived from audit explanations.
---
### 3) Job dispatcher + worker pipeline improvements
**Goal:** Make background job handling explicit, testable, and safer on retries.
**Key additions:**
- Job framework:
- `app/jobs/dispatcher.py` (routes `job_type` → handler)
- `app/jobs/definitions.py`
- `app/jobs/__init__.py`
**Notable behaviors:**
- Optional **idempotency key** support (Redis `SETEX`, 24h TTL) to avoid double-processing when messages are re-delivered.
---
### 4) Demo/dev UX improvements
**Goal:** Run and validate the service without external auth dependencies in dev/demo scenarios.
**Key additions:**
- `app/auth/dependencies.py`
- `no_auth_mode` (always treat caller as `demo_user_id`)
- `demo_auth_enabled` + token value `demo` shortcut
- `app/main.py`
- `GET /demo` serves `app/static/demo.html` (fallback redirects to `/docs`)
- `app/static/demo.html` (mini UI)
---
## What changed (behavioral refactors)
### APIs: “fat routes” ➜ use-case driven routes
Many endpoints were refactored to:
- Stop doing SQLAlchemy querying/ownership checks directly inside routers.
- Delegate to use-cases via `provide_use_case(...)`.
**Examples of refactored routers:**
- `app/api/v1/profiles.py`
- `app/api/v1/content.py`
- `app/api/v1/internal.py`
- Dashboard state endpoint in `app/main.py` now delegates to `app/use_cases/get_dashboard.py`
**Net effect:** Controllers become thinner and easier to reason about; business and persistence logic becomes reusable and testable.
---
### Scoring logic: service module ➜ pure engine module
- Previous: `app/services/scoring_service.py` contained large amounts of pure scoring logic.
- Now: `app/services/scoring_service.py` is a **compatibility wrapper** that re-exports scoring functions/constants from `app/engine/scoring_rules.py`.
**Why:** Centralizes domain logic in the Engine layer and keeps import stability for older code.
---
## What was removed (or relocated)
### 1) `app/services/branding_intelligence.py` removed
- Deleted: `app/services/branding_intelligence.py`
- Replaced by layered structure:
- Added: `app/adapters/branding_intelligence.py`
This is part of a broader “**services → adapters**” refactor.
### 2) Service modules moved under adapters
Renames (logical move; functionality preserved):
- `app/services/ai_service.py` → `app/adapters/ai_service.py`
- `app/services/brightdata_service.py` → `app/adapters/brightdata_service.py`
- `app/services/scrapetable_service.py` → `app/adapters/scrapetable_service.py`
---
## Architecture: before vs after
### Previous architecture (main)
**Common traits observed in the diff:**
- Routers frequently performed:
- DB access (`select`, `update`, joins)
- Ownership/security checks
- Data merging/enrichment logic
- Commit/flush patterns
- Most domain logic lived in *service* modules, often mixing:
- domain rules
- provider orchestration
- persistence decisions
**Typical pain points:**
- Harder to test logic without DB + IO.
- Harder to reuse workflows between:
- HTTP routes
- background jobs
- agent/worker flows
- High coupling between external providers and business logic.
---
### Current architecture (feature-update)
The code is reorganized around clearer boundaries:
1) **Adapters** (`app/adapters/`)
- External IO: AI providers, BrightData, Scrapetable, branding intelligence provider glue.
2) **Use-cases** (`app/use_cases/`)
- Application workflows (orchestration):
- validate inputs
- call repos, engines, adapters
- decide persistence / commit points
3) **Repositories** (`app/repositories/`)
- Database access patterns:
- fetch by clerk id
- fetch profile for user
- save scores/drafts
4) **Engine** (`app/engine/`)
- Pure or mostly-pure domain logic:
- scoring rules
- auditing
- persona generation
- calendar/draft logic
5) **API controllers** (`app/api/...`)
- Translate HTTP requests to use-case calls.
- Convert errors to HTTP codes.
---
## Why the new architecture is better
### 1) Testability
- Engine logic can be unit-tested without network or DB.
- Use-cases can be tested with mocked adapters and in-memory db sessions.
### 2) Separation of concerns (less coupling)
- External providers are isolated in adapters.
- Domain logic is isolated in engine modules.
- DB access is consolidated and reusable in repositories.
### 3) Reuse across execution contexts
The same use-cases can be invoked from:
- REST APIs
- workers/jobs
- agent flows
This reduces duplication and “slightly-different” implementations.
### 4) Safer background jobs
- Dispatcher introduces structured routing and idempotency support.
- Makes retries less risky and easier to reason about.
### 5) Better dev ergonomics
- No-auth mode and a demo UI make local validation faster.
- Less dependency on Clerk for basic smoke tests.
---
## Developer-facing changes to be aware of
### Authentication behavior
- `app/auth/dependencies.py` now supports:
- **global no-auth mode** (treat all calls as `demo_user_id`)
- **demo token** shortcut (token == `demo`)
### DB migrations now required
- Brand/QScore summaries require running Alembic migrations before using those features.
### Import path changes
- External integrations moved under `app/adapters/*`.
- Scoring logic moved to `app/engine/scoring_rules.py` (service remains as a compatibility re-export).
---
## Tests added/updated
**New tests:**
- `tests/test_engine.py`
- `tests/test_hardening.py`
- `tests/test_qscore_integration.py`
- `tests/test_rqx_formula.py`
- `tests/test_worker_convergence.py`
**Updated tests:**
- `tests/test_branding_intelligence.py`
---
## Documentation added
A significant set of planning/architecture docs was added, including:
- `docs/PRD.md`
- `docs/qscore-integration.md`
- `docs/eventing-contract.md`
- plus multiple architecture/plan/audit checklists under `docs/`
---
## Quick file index (most important additions)
- Engine: `app/engine/*`
- Use-cases: `app/use_cases/*`
- Repositories: `app/repositories/*`
- QScore: `app/api/v1/qscore.py`, `app/services/qscore_service.py`, `app/clients/qscore_client.py`, `app/schemas/qscore.py`
- DB migrations: `alembic/versions/20260325_000001_*`, `alembic/versions/20260325_000002_*`
- Demo: `app/static/demo.html`, `GET /demo` in `app/main.py`
- Jobs: `app/jobs/dispatcher.py`
+396
View File
@@ -0,0 +1,396 @@
**GrowQR**
**SOCIAL BRANDING**
**DEVELOPER BRD — TECHNICAL SPECIFICATION**
Individual Social Branding • Enterprise Social Branding • 4 Platforms • 6 Personas • v1.0 • March 2026
| Social Branding Microservices This document contains specifications, business logic, and acceptance criteria for development. |
| :---: |
| Document Title | Social Branding Service — Developer BRD Technical Specification |
| :---- | :---- |
| Version | 1.0 — March 2026 |
| :---- | :---- |
| Service Type | Standalone Microservice — Part of Opportunity Module |
| :---- | :---- |
| Target Platforms | LinkedIn | Instagram | Twitter/X | YouTube |
| :---- | :---- |
| Target Launch | April 1st — LinkedIn, Instagram, YouTube, Twitter/X , Git |
| :---- | :---- |
| Part 1 | Individual Social Branding — 6 end-goal personas, full automation with approval |
| :---- | :---- |
| Part 2 | Enterprise Social Branding — Company brand presence, governance, multi-platform management |
| :---- | :---- |
| Q-Score Integration | Brand Score (0–100) feeds RQx. Q-Score changes trigger automatic profile and content updates. |
| :---- | :---- |
| Data Collection | Method TBD by technical team — BRD specifies data requirements only |
| :---- | :---- |
| PART 1 — INDIVIDUAL SOCIAL BRANDING |
| :---: |
Part 1 covers the Social Branding service for individual GrowQR users. It is a fully automated, AI-managed presence builder across LinkedIn, Instagram, Twitter/X, and YouTube — with user approval at every execution step.
| 1\. SERVICE OVERVIEW — INDIVIDUAL |
| :---- |
The Individual Social Branding service transforms a user’s professional presence from a passive profile into an active, consistent, and strategically managed brand. The service operates on a Scrape → Analyse → Generate → Approve → Post → Track loop, fully managed by GrowQR’s AI layer.
The service is built on five structural pillars drawn from proven social branding principles:
| Pillar | Definition | How GrowQR Implements It |
| :---- | :---- | :---- |
| Consistency | Uniform brand voice, visual style, and messaging across all platforms | AI enforces tone, vocabulary, and visual style rules per persona across all connected platforms |
| Target Audience | Understanding the social habits of the user’s ideal audience (recruiters, clients, peers, investors) | Audience profile built from user’s pathway archetype, target role, and Q-Score profile |
| Content Strategy | A defined mix of educational, entertaining, and inspirational content mapped to brand values | 80/20 content model: 80% pre-baked persona toolkit, 20% dynamic AI-generated content |
| Engagement | Active interaction with followers: likes, comments, DMs, connection requests | AI drafts engagement responses; user approves or auto-approves based on engagement rules |
| Platform Selection | Right platforms for the user’s archetype and target audience | Archetype-conditional platform activation — not all platforms recommended to all users |
## **1.1 The Automation Loop**
| Stage | Action | Human Touch Point |
| :---- | :---- | :---- |
| 1\. Connect | User connects social accounts via OAuth | One-time setup per platform |
| 2\. Scrape / Collect | System collects profile data, post history, engagement metrics, follower data | None — automated |
| 3\. Analyse | AI analyses profile completeness, content performance, audience alignment, Brand Score calculated | User reviews Brand Score dashboard |
| 4\. Generate | AI generates: profile rewrites, content calendar, post drafts, engagement responses | User reviews all generated content |
| 5\. Approve | User approves, edits, or rejects each item in the approval queue | Primary user interaction point |
| 6\. Post / Execute | Approved content scheduled and posted. Profile updates applied. Engagement responses sent. | None — automated post-approval |
| 7\. Track | System monitors: impressions, profile views, follower growth, engagement rate, recruiter search appearances | User reviews weekly analytics dashboard |
| 8\. Feed Back | Performance data feeds Brand Score and RQx Q-Score. Pathway engine updates recommendations. | None — automated |
| 2\. BRAND SCORE — THE SOCIAL Q-SCORE |
| :---- |
Brand Score is a single 0–100 metric that measures the strength, consistency, and discoverability of a user’s professional presence across all connected platforms. It is GrowQR’s proprietary social presence measurement and feeds directly into RQx (Reputation Quotient) in the Q-Score engine.
| Brand Score is not a vanity metric. It is calculated from objective, measurable signals. As the user takes actions recommended by the Social Branding service, their Brand Score improves. Brand Score improvement directly improves their RQx Q-Score, which in turn updates their pathway recommendations. |
| :---- |
## **2.1 Brand Score Components**
| Component | Weight | What It Measures |
| :---- | :---- | :---- |
| Profile Completeness | 20% | All profile sections populated, keyword-optimised, and aligned to target role across active platforms |
| Content Consistency | 20% | Posting frequency maintained, tone consistent, visual style uniform across platforms |
| Audience Growth | 15% | Follower/connection growth rate over 30-day rolling window |
| Engagement Rate | 15% | Likes, comments, shares, and saves as a percentage of reach per post |
| Recruiter Visibility | 15% | Profile appearance rate in relevant recruiter and talent searches (LinkedIn primary) |
| Content Quality | 10% | AI-assessed relevance, originality, and alignment to persona content framework |
| Cross-Platform Consistency | 5% | Messaging and voice alignment across all connected platforms |
## **2.2 Brand Score → RQx Integration**
* Brand Score calculated after every platform data sync (example: frequency: daily for LinkedIn, 48hrs for others)
* Brand Score mapped to RQx using a defined conversion formula: RQx contribution \= Brand Score × 0.4 (40% weighting of RQx from Brand Score)
* RQx update event emitted to Assessment Service and Pathways Service on every Brand Score change of 5+ points
* Pathways engine re-evaluates Social Branding touchpoint recommendations on RQx update
* User’s Brand Score history stored and displayed as a trend line on the Social Branding dashboard
| 3\. SIX END-GOAL PERSONAS — CONTENT FRAMEWORK |
| :---- |
Every user’s Social Branding strategy is driven by their end-goal persona. The persona is derived from the user’s active pathway archetype, target role, and career goal input. Users can switch personas as their goals evolve. Each persona has a defined platform mix, content angle, and posting frequency.
| Platform activation is archetype-conditional. Not all platforms are recommended to all users. The system activates platforms relevant to the user’s persona and prompts the user to connect them. Users can override and connect any platform manually. |
| :---- |
| PERSONA 1: JOB SEEKER Archetype: All archetypes — active job search phase |
| :---- |
| **Recommended Platforms** LinkedIn (primary), Twitter/X (secondary for tech/creative roles) **Content Angle** Recruiter visibility, skills signalling, open-to-work positioning, achievement highlights **Posting Frequency** LinkedIn: 4x per week. Twitter/X: 3x per week if active. |
| PERSONA 2: CAREER TRANSITIONER Archetype: All archetypes — changing domain or role type |
| :---- |
| **Recommended Platforms** LinkedIn (primary), Instagram (if transitioning to creative/coaching/consulting) **Content Angle** Narrative reframing: connecting past experience to new direction, learning journey documentation **Posting Frequency** LinkedIn: 3x per week. Instagram: 2x per week if active. |
| PERSONA 3: THOUGHT LEADER Archetype: Strategic Leader, Entrepreneurial Builder, Domain Expert |
| :---- |
| **Recommended Platforms** LinkedIn (primary), Twitter/X (primary), YouTube (secondary) **Content Angle** Industry insights, opinion pieces, trend analysis, frameworks and models, contrarian takes **Posting Frequency** LinkedIn: 5x per week. Twitter/X: daily. YouTube: 1x per month minimum. |
| PERSONA 4: DOMAIN EXPERT Archetype: Technical Specialist, Creative Innovator |
| :---- |
| **Recommended Platforms** LinkedIn (primary), GitHub (external link), YouTube (tutorials/walkthroughs) **Content Angle** Deep technical content, how-to posts, case studies, project showcases, certification highlights **Posting Frequency** LinkedIn: 3x per week. YouTube: 2x per month. |
| PERSONA 5: ENTREPRENEUR / FOUNDER Archetype: Entrepreneurial Builder |
| :---- |
| **Recommended Platforms** LinkedIn (primary), Twitter/X (primary), Instagram (brand building), YouTube (storytelling) **Content Angle** Build-in-public content, startup journey, lessons learned, team culture, product milestones **Posting Frequency** LinkedIn: 5x per week. Twitter/X: daily. Instagram: 3x per week. YouTube: 2x per month. |
| PERSONA 6: EXECUTIVE / ENTERPRISE LEADER Archetype: Strategic Leader — VP, Director, C-suite level |
| :---- |
| **Recommended Platforms** LinkedIn (primary), Twitter/X (secondary) **Content Angle** Executive presence: strategic vision, culture and values, industry leadership, org transformation, speaking engagements **Posting Frequency** LinkedIn: 3x per week. Twitter/X: 2x per week. |
| 4\. THE 80/20 CONTENT MODEL |
| :---- |
All content generated by GrowQR’s Social Branding service follows an 80/20 split: 80% pre-baked toolkit content and 20% dynamic AI-generated content. This balance ensures consistency and quality (80%) while maintaining relevance and freshness (20%).
## **4.1 80% — Pre-Baked Toolkit**
The pre-baked toolkit is a library of proven content frameworks, post templates, hooks, and evergreen structures mapped to each persona and platform. This content is designed, tested, and maintained by GrowQR. It does not require AI generation — it is filled with user-specific data at execution time.
| Content Type | Description | Persona Applicability |
| :---- | :---- | :---- |
| Achievement Post | Structured template: milestone \+ what it took \+ what you learned. Fills with user’s real achievements from pathway completions. | All personas |
| Skills Signal Post | Highlights a specific skill or certification. Links to Q-Score improvement data. | Job Seeker, Domain Expert, Career Transitioner |
| Insight Share | Short opinion or observation on an industry trend. Structure: Hook \+ Insight \+ Question to audience. | Thought Leader, Executive, Entrepreneur |
| Career Story Post | Narrative format: where I was \+ what changed \+ where I am now. Used for transitions and milestones. | Career Transitioner, Entrepreneur |
| Project Showcase | Work sample or project result with context. Visual-first for Instagram; text+image for LinkedIn. | Domain Expert, Creative Innovator, Entrepreneur |
| Engagement Prompt | Question or poll to drive comments. Mapped to user’s domain for relevant audience response. | All personas |
| Weekly Reflection | End-of-week summary: what I worked on, what I learned, what’s next. | Thought Leader, Entrepreneur, Executive |
| Company/Role Spotlight | Highlights a company or role the user is targeting or has experience with. Builds recruiter relevance. | Job Seeker, Career Transitioner |
## **4.2 20% — Dynamic Content**
Dynamic content is AI-generated in real-time based on signals from the user’s platform activity, Q-Score progress, pathway milestones, and trending topics in their domain. This content is fresh, timely, and personalised — it cannot be pre-baked.
* Q-Score improvement post: auto-generated when user improves a Q-Score by 10+ points. Frames the improvement as a professional growth story.
* Pathway milestone post: auto-generated when user completes a course, earns a certificate, or achieves a pathway milestone.
* Trending topic response: AI monitors trending topics in the user’s domain. Generates a short opinion post when a relevant trend is detected.
* Interview or job application event: auto-generated encouragement and visibility post when user applies for a role or secures an interview (with user consent).
* Engagement response drafts: AI drafts replies to comments on the user’s posts, maintaining persona tone and voice.
* Profile update trigger: when user’s Q-Scores or pathway status changes significantly, AI generates updated headline, about section, and skills list for user approval.
## **4.3 Content Calendar Generation Rules**
| Rule | Description |
| :---- | :---- |
| Rule 1: Frequency Enforcement | System enforces minimum posting frequency per persona. If user has not approved content for upcoming slots, system sends reminder 48hrs in advance. |
| Rule 2: Platform Sequencing | Content is not duplicated across platforms. Each platform receives a uniquely formatted version adapted to that platform’s style and audience. |
| Rule 3: Content Mix Enforcement | System ensures the 80/20 split is maintained over any 2-week rolling window. Prevents over-reliance on either toolkit or dynamic content. |
| Rule 4: No Auto-Post Without Approval | No content is ever posted without explicit user approval. Auto-approve mode is available as an opt-in setting only. |
| Rule 5: Tone Lock | Once user’s brand voice is established (after first 2 weeks), tone settings are locked unless user explicitly resets them. |
| Rule 6: Sensitive Topic Filter | AI applies a topic filter before generating content. Posts that touch on politically sensitive, legally risky, or reputationally harmful territory are flagged and not queued for approval. |
| 5\. PLATFORM SPECIFICATIONS — INDIVIDUAL |
| :---- |
## **5.1 LinkedIn**
| Capability | Specification |
| :---- | :---- |
| Profile Analysis | Audit: Headline, About, Experience, Skills, Recommendations, Featured section, Activity. Score each section 0–100. Benchmark against target role requirements and top 10% of professionals in same role. |
| Profile Rewrite | AI generates optimised versions of: Headline (3 variants), About section, Experience bullet points (per role), Skills list (mapped to Q-Scores and target JD keywords). User selects and approves. |
| Recruiter Visibility | Keyword gap analysis: identifies terms recruiters search for this role that are missing from profile. Injects keywords into profile sections naturally. |
| Content Posting | Supports: text posts, image posts, document carousels, polls, article links. Content formatted to LinkedIn algorithm best practices (hook in first 2 lines, no external links in body). |
| Connection Strategy | Identifies target connections based on user’s pathway: target companies, role peers, hiring managers in target domain. AI drafts personalised connection request notes. |
| Engagement Automation | AI drafts comments on posts from target connections and industry leaders. User approves before sending. Builds visibility with relevant audience. |
| Analytics Tracked | Profile views (7-day, 30-day), Search appearances, Post impressions, Engagement rate per post, Connection acceptance rate, Follower growth |
## **5.2 Instagram**
| Instagram is activated for: Career Transitioner (creative/coaching roles), Entrepreneur/Founder, Domain Expert (creative/design roles). Not recommended by default for Technical Specialist or Strategic Leader personas unless user opts in. |
| :---- |
| Capability | Specification |
| :---- | :---- |
| Profile Analysis | Bio, profile photo, link-in-bio, highlight covers, grid aesthetic consistency, story frequency. Score for professional brand alignment. |
| Content Types | Feed posts (static image, carousel), Reels (short-form video), Stories (ephemeral). Each type has distinct content templates in the pre-baked toolkit. |
| Visual Style Consistency | AI enforces visual brand rules: colour palette (derived from user’s LinkedIn brand colours), font style, filter consistency. Applied to all generated visual templates. |
| Caption Framework | Each caption follows: Hook (first line visible before ‘more’) \+ Value (body) \+ CTA (final line) \+ Hashtags (max 5, domain-specific). Hashtag bank maintained per persona. |
| Story Strategy | Stories used for: behind-the-scenes content, quick tips, polls, Q\&As. Frequency: 3–5 stories per week. Story templates provided per persona. |
| Analytics Tracked | Reach, Impressions, Profile visits, Follower growth, Story views, Reel plays, Link-in-bio clicks, Saves per post |
## **5.3 Twitter / X**
| Twitter/X is targeted for Phase 2 launch. Architecture and data model specified here for development readiness. April 1st launch excludes Twitter/X. |
| :---- |
| Capability | Specification |
| :---- | :---- |
| Profile Analysis | Bio (160 chars), header image, pinned tweet, posting frequency, engagement rate, follower-to-following ratio. |
| Content Types | Short posts (under 280 chars), Threads (multi-tweet narrative), Quote posts (with commentary), Replies (engagement strategy). |
| Thread Framework | Threads used for: insight breakdowns, how-to guides, career lessons, domain deep-dives. Format: Hook tweet \+ 5–10 body tweets \+ CTA final tweet. |
| Posting Strategy | Optimal posting times calculated per user’s audience timezone. Threads on weekdays, shorter posts and replies on weekends. |
| Analytics Tracked | Impressions, Engagements, Profile clicks, Link clicks, Follower growth, Thread completion rate |
## **5.4 YouTube**
| Capability | Specification |
| :---- | :---- |
| Channel Analysis | Channel art, About section, playlist structure, video titles and descriptions (SEO), thumbnail consistency, upload frequency, subscriber growth rate. |
| Content Types | Long-form (10–20 min): deep dives, case studies, tutorials. Short-form (YouTube Shorts, under 60 sec): tips, quick insights, career advice clips. |
| Video Brief Generation | AI generates video briefs: title (3 variants), hook script (first 30 seconds), chapter outline, description with keywords, tags, thumbnail concept. User records; AI does not generate video. |
| SEO Optimisation | Title keyword analysis, description keyword injection, tag strategy, chapter timestamps. Benchmarked against top-performing videos in user’s domain. |
| Thumbnail Strategy | AI generates thumbnail design brief: text overlay, colour scheme, facial expression guidance (if talking head). Thumbnail template provided per persona. |
| Upload Frequency | Persona-based targets: Thought Leader (1x/month minimum), Domain Expert (2x/month), Entrepreneur (2x/month). System prompts user when cadence is falling behind. |
| Analytics Tracked | Views, Watch time, Subscriber growth, Click-through rate (thumbnails), Average view duration, Top traffic sources |
| 6\. DATA REQUIREMENTS — INDIVIDUAL |
| :---- |
| Data collection method (API vs scraping vs user-authenticated export) is TBD and left to the technical team. This section specifies what data is required. The technical team must define the collection mechanism, document any legal or rate-limit constraints, and confirm data availability before development of the collection layer. |
| :---- |
| Platform | Data Required | Frequency |
| :---- | :---- | :---- |
| LinkedIn | Profile sections (headline, about, experience, education, skills, certifications, featured, recommendations). Post history (last 90 days). Engagement per post (likes, comments, shares). Profile view count. Search appearance count. Connection count. Follower count. | Daily sync |
| Instagram | Bio, profile photo URL, follower count, following count, post count. Feed posts (last 60 days): caption, image URL, likes, comments, saves, reach, impressions. Story views (last 30 days). Reel plays. | 48-hour sync |
| Twitter/X | Bio, follower count, following count. Tweet history (last 60 days): text, impressions, engagements, link clicks, retweets, replies. Profile visits. | 48-hour sync (Phase 2\) |
| YouTube | Channel stats: subscriber count, total views, video count. Per-video: title, description, tags, views, watch time, CTR, average view duration, subscriber change. Upload dates. | Weekly sync |
| PART 2 — ENTERPRISE SOCIAL BRANDING |
| :---: |
Part 2 covers the Social Branding service for companies and organisations. An enterprise account manages its brand presence across social platforms — defining what to post, what not to post, maintaining brand voice consistency, and managing AI-assisted content creation and scheduling across multiple platforms with an internal approval workflow.
| 7\. SERVICE OVERVIEW — ENTERPRISE |
| :---- |
Enterprise Social Branding gives companies a governed, AI-assisted platform to manage their professional brand presence. It is distinct from the individual service in three fundamental ways: it serves an organisation rather than a person, it includes multi-user approval workflows (not just single-user approval), and it includes brand governance rules that define what content is and is not acceptable before the AI generates anything.
| Dimension | Individual Social Branding | Enterprise Social Branding |
| :---- | :---- | :---- |
| Subject | A person’s professional brand | A company’s brand presence |
| Platforms | Personalised per archetype and persona | Standardised across all company-connected platforms |
| Content strategy | Persona-driven, career-goal aligned | Brand voice and values-driven, audience-targeted |
| Approval workflow | Single user approves | Multi-role workflow: Creator → Editor → Approver |
| Brand governance | Tone rules per persona | Explicit content policy: what to post, what not to post |
| Analytics focus | Individual brand growth, recruiter visibility | Company reach, audience growth, brand sentiment |
| Q-Score integration | Feeds RQx for individual | Not Q-Score integrated — separate enterprise analytics |
## **7.1 Enterprise Account Structure**
* An enterprise account is associated with a company entity (not an individual user)
* Multiple team members can be added to the enterprise account with defined roles
* Three roles: Content Creator (drafts content), Content Editor (reviews and edits), Brand Approver (final approval before posting)
* At least one Brand Approver must be assigned before any content can go live
* Enterprise account connects company social profiles (LinkedIn Company Page, Instagram Business, Twitter/X Business, YouTube Channel)
| 8\. BRAND GOVERNANCE FRAMEWORK — ENTERPRISE |
| :---- |
Brand governance is the defining capability of the Enterprise service. Before the AI generates any content, it operates within a set of brand rules defined by the company. These rules determine what gets created, what gets filtered, and what gets flagged for human review.
## **8.1 Brand Voice Definition**
The enterprise sets its brand voice at account setup. Brand voice is defined across five dimensions:
| Dimension | Options | Example |
| :---- | :---- | :---- |
| Tone | Professional / Conversational / Authoritative / Inspirational / Technical | A fintech company selects: Professional \+ Authoritative |
| Formality | Formal / Semi-formal / Casual | Most enterprises: Semi-formal |
| Perspective | First person (we) / Third person (company name) | Most LinkedIn company posts: First person (we) |
| Language style | Simple and direct / Rich and descriptive / Data-led / Story-led | Tech companies often: Simple and direct \+ Data-led |
| Audience | Talent/Recruitment / Clients/Customers / Industry peers / Investors / General public | Can select multiple; content adapted per audience tag |
## **8.2 Content Policy — What to Post**
* Company milestones: product launches, funding announcements, partnerships, awards
* Thought leadership: industry trends, company perspective on market developments, research and data
* Culture and values: team stories, behind-the-scenes, company events, diversity and inclusion content
* Product and service highlights: feature announcements, case studies, client success stories (with consent)
* Recruitment content: open roles, employer brand, employee testimonials, workplace benefits
* Educational content: how-to guides, industry education, tips relevant to the company’s domain
* Engagement content: polls, questions, community challenges aligned to brand values
## **8.3 Content Policy — What Not to Post (Governance Rules)**
The following rule categories are configurable by the enterprise at account setup. The AI applies these rules as filters before generating any content. Content that violates a rule is not queued — it is discarded or flagged for human review.
| Rule Category | Default Setting | Configurable? |
| :---- | :---- | :---- |
| Political statements or partisan content | Blocked — never generated | Yes — enterprise can unlock with justification |
| Commentary on competitors by name | Blocked — never generated | Yes — enterprise can unlock for specific approved comparisons |
| Unverified claims or statistics | Flagged for human review before queuing | No — always flagged |
| Sensitive social topics (religion, race, gender commentary) | Flagged for human review | Yes — enterprise can block entirely |
| Legal or compliance-sensitive content (regulatory, financial) | Flagged for legal review tag | No — always flagged |
| Crisis or incident response content | Blocked from AI generation — must be human-authored | No — always blocked from AI |
| Content featuring named individuals (external) | Requires explicit approval flag | No — always requires approval |
| Off-brand tone (outside defined voice dimensions) | Regenerated automatically before queuing | No — always enforced |
| 9\. ENTERPRISE CONTENT ENGINE |
| :---- |
## **9.1 Content Generation Flow**
| Step | Action | Role Responsible | System Behaviour |
| :---- | :---- | :---- | :---- |
| 1 | Content brief input | Content Creator or AI-initiated | Creator provides topic/theme OR system auto-generates brief from content calendar schedule |
| 2 | Brand governance check | System (automated) | Brief checked against content policy rules. Blocked topics discarded. Flagged topics tagged for review. |
| 3 | AI content generation | System (automated) | AI generates post draft applying: brand voice rules, platform format requirements, 80/20 content model, audience targeting |
| 4 | Creator review | Content Creator | Creator reviews draft, makes edits, adds platform-specific adjustments |
| 5 | Editor review | Content Editor | Editor reviews for brand alignment, tone consistency, factual accuracy |
| 6 | Brand approval | Brand Approver | Final approval. Can approve, request changes, or reject. Only Brand Approver can push to scheduled. |
| 7 | Schedule and post | System (automated) | Approved content scheduled per platform optimal posting time. Posted automatically at scheduled time. |
| 8 | Performance tracking | System (automated) | Impressions, engagement, follower data collected. Brand analytics dashboard updated. |
## **9.2 Enterprise 80/20 Content Model**
The same 80/20 content model applies to enterprise, with enterprise-specific content types:
| Content Type | Category | Description |
| :---- | :---- | :---- |
| Company milestone post | 80% Toolkit | Template: announcement \+ context \+ what it means for customers/team \+ CTA. Fills with company-specific data. |
| Thought leadership post | 80% Toolkit | Template: industry observation \+ company perspective \+ supporting data point \+ question to audience. |
| Culture post | 80% Toolkit | Template: team story or value highlight \+ what it demonstrates about the company \+ employee quote (with consent). |
| Job opening post | 80% Toolkit | Template: role title \+ why this role matters \+ what kind of person thrives here \+ apply link. |
| Educational post | 80% Toolkit | Template: common misconception or question in domain \+ company perspective \+ practical takeaway. |
| Trending topic response | 20% Dynamic | AI monitors industry news. Generates company perspective post when relevant trend detected. Always flagged for Brand Approver review. |
| Milestone reaction | 20% Dynamic | AI auto-generates draft when company milestone event detected (e.g. funding announcement, award won). Requires full approval workflow. |
| Engagement response | 20% Dynamic | AI drafts responses to comments on company posts. Creator reviews before sending. |
| 10\. ACCEPTANCE CRITERIA |
| :---- |
**Part 1 — Individual Social Branding**
| \# | Acceptance Criterion | Pass / Fail |
| :---- | :---- | :---- |
| **1** | OAuth connection completes for all four platforms (LinkedIn, Instagram, Twitter/X, YouTube). Profile data collected on first sync. Sync schedule runs per defined frequency. | \[ \] PASS \[ \] FAIL |
| **2** | Brand Score calculated correctly from all 7 components. Brand Score feeds RQx via defined formula. RQx update event emitted to Assessment and Pathways services on Brand Score change of 5+ points. | \[ \] PASS \[ \] FAIL |
| **3** | Profile analysis runs for all four platforms. Section-by-section audit scores generated. Benchmark against target role applied. Profile recommendations generated with current and recommended text. | \[ \] PASS \[ \] FAIL |
| **4** | Profile recommendations can be approved and applied to the live platform profile. Rejected recommendations discarded. Applied recommendations update the social\_profile\_recommendations table correctly. | \[ \] PASS \[ \] FAIL |
| **5** | Content calendar generated for correct persona with correct platform mix. 80/20 split enforced over any 2-week rolling window. All 6 content calendar rules applied correctly. | \[ \] PASS \[ \] FAIL |
| **6** | Pre-baked toolkit content fills correctly with user-specific data (achievements, Q-Score stats, pathway milestones). All 8 toolkit content types functional. | \[ \] PASS \[ \] FAIL |
| **7** | Dynamic content triggers fire correctly: Q-Score improvement event, pathway milestone event, trending topic detection, profile update trigger. | \[ \] PASS \[ \] FAIL |
| **8** | Approval queue functions correctly: user can approve (with optional edits), reject, or request regeneration for each content item. No content posted without approved status. | \[ \] PASS \[ \] FAIL |
| **9** | Auto-approve mode available as opt-in only. When enabled, content posts without individual approval. Can be disabled at any time. | \[ \] PASS \[ \] FAIL |
| **10** | Approved content posted to correct platform at scheduled time. Platform-specific formatting applied (LinkedIn: no external link in body. Instagram: hashtags appended. YouTube: description and tags generated.) | \[ \] PASS \[ \] FAIL |
| **11** | Engagement queue populated with AI-drafted responses to comments and connection requests. User approval required before sending. | \[ \] PASS \[ \] FAIL |
| **12** | Analytics collected per platform per sync cycle. Performance data stored in social\_content\_performance table. Analytics dashboard displays all tracked metrics with trend lines. | \[ \] PASS \[ \] FAIL |
| **13** | Persona switching updates platform activation recommendations, content calendar strategy, and posting frequency rules immediately. | \[ \] PASS \[ \] FAIL |
| **14** | Archetype-conditional platform activation: Instagram not recommended by default for Technical Specialist or Strategic Leader personas. System surfaces recommendation prompt for relevant personas only. | \[ \] PASS \[ \] FAIL |
**Part 2 — Enterprise Social Branding**
| \# | Acceptance Criterion | Pass / Fail |
| :---- | :---- | :---- |
| **1** | Enterprise account creation completes with brand voice and content policy configuration. Multi-role team setup (Creator, Editor, Approver) functional. Minimum one Approver enforced before content can go live. | \[ \] PASS \[ \] FAIL |
| **2** | Brand governance rules applied before AI content generation. Blocked topics discarded without queuing. Flagged topics tagged and routed to enterprise\_content\_flags table for human review. | \[ \] PASS \[ \] FAIL |
| **3** | All 8 governance rule categories enforced: political content blocked, competitor mentions blocked, unverified claims flagged, sensitive topics flagged, legal content flagged, crisis content blocked from AI, named individuals require approval, off-brand tone regenerated. | \[ \] PASS \[ \] FAIL |
| **4** | Brand voice rules (tone, formality, perspective, language style, audience) applied to all AI-generated content. Off-brand drafts regenerated automatically before entering approval queue. | \[ \] PASS \[ \] FAIL |
| **5** | Three-stage approval workflow enforced: Creator → Editor → Brand Approver. Content cannot skip stages. Only Brand Approver role can push content to scheduled status. | \[ \] PASS \[ \] FAIL |
| **6** | All 8 enterprise content types (5 toolkit \+ 3 dynamic) generated correctly. Trending topic and milestone dynamic content always routed to Brand Approver regardless of workflow stage. | \[ \] PASS \[ \] FAIL |
| **7** | Flagged content resolution workflow completes: flag raised → human reviews → approved/rejected/edited → flag closed. Content entry status updates correctly on resolution. | \[ \] PASS \[ \] FAIL |
| **8** | Enterprise analytics collected per platform. Brand analytics dashboard displays follower growth, impressions, engagement rate, and top-performing posts per platform. | \[ \] PASS \[ \] FAIL |
| **9** | Enterprise content calendar visible across all team member roles with correct status labels per approval stage. Each role sees only the actions available to their role. | \[ \] PASS \[ \] FAIL |
@@ -0,0 +1,458 @@
# Social Branding Service Audit — Architecture, Integration, Plan/PRD Alignment
Date: 2026-03-25
## Purpose
This report updates the prior audit (`docs/plan-prd-implementation-audit.md`, dated 2026-03-23) with:
- a detailed review of **layering leaks / integration issues** (surface vs service vs engine)
- a clear summary of **how far we’ve progressed since the prior audit**
- a mapping of the current implementation against:
- the **narrow plan** in `docs/plan.md`
- the broader **PRD** in `docs/PRD.md`
- an explicit **acceptance criteria coverage** summary (met vs not met)
Auth is treated as non-blocking unless it materially affects architecture boundaries.
---
## Executive Summary
The architecture is **significantly more aligned** to the desired Engine → Service (Use-cases) → Surface split than it was in the 2026-03-23 audit.
Key improvements versus the prior audit:
- an explicit `app/engine/` package now exists with normalized I/O contracts (`app/engine/contracts.py`)
- API routes mostly delegate to `app/use_cases/*` orchestration modules
- persistence is generally accessed via `app/repositories/*`
- Redis worker has a functional job execution path (`job_type`) and idempotency semantics
- migrations are now the schema source of truth (no startup `create_all()` behavior)
However, there are still **layering leaks** and clarity issues:
- the engine layer depends on `app/services/scoring_service.py` (a layer inversion)
- `app/services/` mixes concerns (domain logic + adapters + AI provider orchestration)
- surfaces still pass DB sessions directly into use-cases (minor leak; helper exists to hide it)
- worker supports both job-based and legacy session-based processing paths (migration state)
Bottom line:
- **Plan alignment:** largely achieved at the capability level, though the API surface has evolved from the plan’s original endpoint list.
- **PRD alignment:** still intentionally partial (LinkedIn-first subset). Cross-platform automation, scheduled posting, RQx integration, and enterprise workflows remain out of scope / incomplete.
---
## Current Architecture (Observed)
### Surface layer
Surfaces are primarily HTTP routes:
- FastAPI app composition: `app/main.py`
- v1 router aggregation: `app/api/v1/router.py`
- route modules:
- `app/api/v1/profiles.py`
- `app/api/v1/analysis.py`
- `app/api/v1/content.py`
- `app/api/v1/internal.py`
Additional surfaces also exist:
- WebSocket agent surface: `app/main.py` → `/ws/agent`
- A2A surfaces: `app/a2a/*`
- MCP tool surface: `app/main.py` mounts `/mcp`
### Service layer (orchestration)
Orchestration is primarily represented by:
- Use-cases: `app/use_cases/*`
- Persistence boundaries: `app/repositories/*`
Example orchestration pattern:
- `app/use_cases/enrich_profile.py`:
- loads user + profile via repos (`AccountsRepo`, `ProfilesRepo`)
- calls an external adapter (`BrightDataService`)
- persists changes via repos
- returns the updated ORM object
### Engine layer (pure decision logic)
The engine now has:
- normalized contracts: `app/engine/contracts.py`
- decision engines:
- `app/engine/audit_engine.py`
- `app/engine/brand_score_engine.py`
- `app/engine/persona_engine.py`
- `app/engine/calendar_engine.py`
- `app/engine/draft_engine.py`
### Worker + jobs
- Worker consumes Redis Stream `tasks:social-branding`: `app/adk/worker.py`
- Jobs are normalized and enumerated: `app/jobs/definitions.py`
- Worker supports:
1. **Job path** (new): `job_type` → jobs dispatcher → use-cases
2. **Legacy session path**: agent session-based behavior
See README job summary: `README.md` (“Job system”).
---
## Progress vs prior audit (2026-03-23 → 2026-03-25)
The previous audit described the repo as:
- HTTP-only in practice
- a single application service mixing persistence + orchestration + business logic
- an engine-like set of helpers, but not separated as explicit engine/package boundary
- Redis and worker as scaffolding only
### What has improved substantially
1) **Engine extraction is real**
- New explicit `app/engine/` package
- New normalized boundary models in `app/engine/contracts.py`
This directly addresses the prior audit’s top architectural mismatch (“no explicit normalized engine contract”).
2) **Service logic is decomposed into use-cases + repositories**
- Routes in `app/api/v1/*` generally call `app/use_cases/*`.
- Use-cases generally rely on repositories for DB access.
This reduces the prior “one large service” problem.
3) **Worker is now an operational path**
- `app/adk/worker.py` supports a job-based path via `job_type`.
- `app/jobs/definitions.py` defines job types and an idempotency key mechanism.
This is stricter and more production-like than the prior “scaffold” characterization.
4) **Schema is migration-managed**
README explicitly states:
- app does not auto-create tables
- Alembic migrations are required
This removes a major “MVP-only” footgun from the earlier audit.
### Verification evidence
- Unit tests: `pytest -q` → **106 passed** (2 warnings)
---
## Layering leaks / integration issues (detailed)
### Leak A — Engine depends on `app/services/*` (layer inversion)
**Evidence**
- `app/engine/audit_engine.py` imports `app.services.scoring_service` inside `run_audit()`.
**Why this is a leak**
The intended architecture says engine is pure and should not depend on service/adapters.
Even if `scoring_service.py` is currently pure, it lives in `app/services/`, which is conceptually “above” engine.
**Risk**
- future edits may add I/O, config, or provider calls into `scoring_service.py`
- engine purity becomes fragile and the architecture story becomes ambiguous
**Recommendation**
Move the scoring rules into the engine layer and reverse dependencies:
- rename/move `app/services/scoring_service.py` → `app/engine/scoring_rules.py` (or similar)
- update `audit_engine.py` to call `app.engine.scoring_rules`
- optionally keep a thin compatibility wrapper in `app/services/scoring_service.py` for external callers
Success criteria:
- `app/engine/*` imports only stdlib + `app.engine.contracts` (+ pydantic)
- no `app.services.*`, no SQLAlchemy, no FastAPI
---
### Leak B — `app/services/` mixes multiple layers (domain logic + adapters + provider orchestration)
**Evidence**
`app/services/` contains:
- domain-like rules logic: `app/services/scoring_service.py`
- external platform adapters:
- `app/services/brightdata_service.py`
- `app/services/scrapetable_service.py`
- AI orchestration / provider layer:
- `app/services/branding_intelligence.py`
- `app/services/ai_service.py`
**Why this matters**
It’s not “wrong” operationally, but it weakens the 3-layer presentation (engine/service/surface).
Right now the actual runtime conceptual layers are closer to:
- surfaces
- use-cases (service orchestration)
- engine
- adapters/providers (BrightData/Scrapetable/OpenAI/DSPy)
**Recommendation**
Split by intent:
- `app/engine/` → pure decision logic
- `app/use_cases/` → orchestration
- `app/repositories/` → persistence boundary
- `app/adapters/` (or `app/integrations/`) → brightdata/scrapetable/ai providers
This is mostly a packaging clarity move, but it reduces future layering violations.
---
### Leak C — Surfaces still pass DB sessions directly (minor leak)
**Evidence**
Routes commonly use:
- `session=Depends(get_session)`
- then call use-cases with `execute(session, clerk_id=..., ...)`
Examples:
- `app/api/v1/analysis.py`
- `app/api/v1/profiles.py`
- `app/api/v1/content.py`
There is already a helper intended to hide this:
- `app/api/dependencies.py` defines `provide_use_case(fn)`
**Why it matters**
This makes surfaces slightly “thicker” and exposes infrastructure plumbing.
**Recommendation**
Optional cleanup:
- adopt `provide_use_case()` consistently so route modules don’t touch sessions directly
---
### Leak D — Worker supports two execution models (job path + legacy session path)
**Evidence**
- `app/adk/worker.py`:
- job path: `job_type` → jobs dispatcher
- legacy path: `SocialAgentSession` based on `action` / `session_start`
**Why it matters**
Having two paths for “doing work” means:
- idempotency/retry semantics may differ
- auth context may differ
- business invariants can diverge
**Recommendation**
Treat this as an explicit migration state:
- long-term goal: agent/session path should call use-cases (or enqueue jobs) so there’s one service boundary
---
## Plan alignment (`docs/plan.md`)
The plan is a LinkedIn-first, individual-only MVP with:
- ingest profile data
- compute Brand Score v1
- persona-based strategy
- content calendar + draft generation
- approval queue + manual publish confirmation
- dashboard exposure
### Capability-level mapping (observed)
1) **Ingest / import**
- URL-first ingestion and refresh flows exist:
- `app/api/v1/profiles.py`
- use-cases: `save_linkedin_url`, `fetch_profile_by_url`, `sync_profile`, `enrich_profile`
- Worker supports `IMPORT_PROFILE` job: `app/jobs/definitions.py`
Status: **Met** (though ingestion method has evolved vs plan’s “manual JSON import endpoint”).
2) **Audit + Brand Score v1**
- rule-based auditing exists:
- `app/services/scoring_service.py`
- `app/engine/audit_engine.py`
- Brand score engine exists:
- `app/engine/brand_score_engine.py`
- brand score use-case exists: `app/use_cases/recalculate_brand_score.py`
Status: **Met**
3) **Persona + calendar + drafts**
- persona engine: `app/engine/persona_engine.py`
- calendar engine: `app/engine/calendar_engine.py`
- draft engine: `app/engine/draft_engine.py`
- calendar use-case: `app/use_cases/generate_calendar.py`
- worker job: `GENERATE_CALENDAR`
Status: **Met**
4) **Approval queue + publish confirmation**
- use-case exists: `app/use_cases/approval_workflow.py`
Status: **Likely met** (use-case exists, test suite passes broadly), but endpoint-level verification should be confirmed if this is presentation-critical.
5) **Dashboard**
- dashboard use-case: `app/use_cases/get_dashboard.py`
- dashboard state endpoint for ecosystem: `app/main.py` → `/api/state/{clerk_id}`
Status: **Met**
### Plan checklist items (from plan.md “Review Checklist”)
- Service works without Live LinkedIn OAuth → **Met** (URL-based ingestion)
- LinkedIn is the only live platform in v1 → **Met** (runtime behavior)
- Brand Score does not depend on unavailable metrics → **Met**
- Approval queue blocks publishing until approval → **Likely met** (verify approval endpoints and state transitions if needed)
- Multi-platform and enterprise features stay deferred → **Met**
- Dashboard exposes useful state → **Met**
---
## PRD alignment (`docs/PRD.md`)
PRD describes a broader multi-platform automation loop:
Connect → Collect → Analyze → Generate → Approve → Post → Track → Feed back (Brand Score → RQx)
### What is covered (partial PRD)
- LinkedIn profile analysis exists (rule-based + AI routes)
- Strategy generation exists (`app/api/v1/content.py` and `app/use_cases/generate_content_strategy.py`)
- Approval concept exists (approval workflow use-case)
- Intelligence provider architecture exists (`app/services/branding_intelligence.py`) with heuristic fallback
### What is not covered / missing vs PRD
Not observed in current codebase (and consistent with MVP scope):
- OAuth connect flows per platform
- Cross-platform runtime collection + posting (Instagram/X/YouTube)
- Scheduled posting executors
- Recurring sync cadence engine (daily/48h/weekly) as an orchestrated scheduler
- Brand Score → RQx formula mapping and cross-service event emission
- string search found no `RQx`, `Assessment Service`, or `Pathways Service` integration hooks in `app/**`
- Tone lock and sensitive-topic filtering
- Auto-approve mode (opt-in)
- Engagement automation workflow (draft replies/comments end-to-end)
- Enterprise governance and multi-role approval workflow
PRD alignment conclusion:
- **PRD-complete:** No
- **PRD-partial (LinkedIn-first subset / foundations):** Yes
---
## Acceptance Criteria Coverage Summary
### Plan acceptance criteria (interpreted from `docs/plan.md`)
Met:
- LinkedIn-first, individual-only MVP flow is implemented in current architecture
- ingestion without OAuth dependency is supported
- audit, persona, calendar, drafts, brand score, dashboard exist
- background job path exists and is documented
- migrations required (no implicit schema creation)
Not fully verified in this pass:
- approval queue endpoints + publish confirmation endpoints (use-case exists; verify routes/state transitions if required for demo)
### PRD acceptance criteria (high-level)
Met (foundation-level only):
- analysis + generation surfaces exist
- approval touchpoint exists conceptually
Not met (major PRD features):
- automated cross-platform loop (connect/collect/post/track)
- scheduled posting
- analytics parity + recurring sync
- Brand Score → RQx integration + downstream event emission
- tone lock, sensitive-topic filter
- auto-approve mode
- engagement automation
- enterprise governance workflows
---
## Quality gates (evidence)
- Tests: `pytest -q` → **106 passed** (2 warnings)
Warnings:
- Pydantic v2 deprecation warning about class-based Config (non-blocking)
---
## Recommendations (prioritized)
### 1) Fix engine purity (remove layer inversion)
- Move rule-based scoring logic from `app/services/scoring_service.py` into `app/engine/`.
- Update `app/engine/audit_engine.py` to call engine-local scoring.
This is the cleanest, highest-signal architecture fix.
### 2) Clarify adapter/provider layer
- Split `app/services/` into `app/adapters/` (or `app/integrations/`) vs domain logic.
### 3) Thin surfaces further (optional)
- adopt `app/api/dependencies.py::provide_use_case()` so routes don’t pass sessions directly.
### 4) Converge worker execution paths
- make legacy session path invoke use-cases (or enqueue jobs) so there is one orchestration boundary.
---
## Bottom line
Compared to 2026-03-23, this repo is now **meaningfully closer** to the desired target architecture:
- explicit engine and normalized contracts
- use-case based orchestration
- worker jobs are functional and documented
The main remaining architecture debt is **layer purity and packaging clarity**, not missing MVP features.
@@ -0,0 +1,376 @@
# Implementation plan — Brand Score: full PRD component model + breakdown
Date: 2026-03-27
Scope: PRD additions item **(1)** (from `prd-additions.md`)
Non-goals (explicit PRD exclusions for this phase): posting, executing actions on platforms, adding new platforms beyond LinkedIn-first.
---
## Checklist (requirements coverage)
- [ ] Add remaining Brand Score components as **first-class computed fields** (not just a single audit-derived score):
- [ ] Content Consistency (20%)
- [ ] Audience Growth (15%)
- [ ] Engagement Rate (15%)
- [ ] Recruiter Visibility (15%)
- [ ] Content Quality (10%)
- [ ] Cross-Platform Consistency (5%) (schema-ready; compute from available platforms only)
- [ ] Store/persist **component breakdown** and PRD-required **“objective, measurable signals”** explanations
- [ ] Implement Brand Score **trend line** (history storage + API response)
- [ ] Implement **v1 proxies + renormalization** for LinkedIn-private analytics gaps
- [ ] Add **metrics snapshot persistence** (followers/connections over time) so Audience Growth + trendline are computable
---
## 1) Outcome + contract
### Inputs
Per `user_id` and a computation window (rolling), compute with:
- LinkedIn profile snapshot: headline/about/experience/skills (as currently ingested)
- Post/activity list over a window (suggest 30 days): timestamps, text, and any available post metrics
- Metrics snapshots over time (if present/collectible): followers/connections, impressions, engagements, profile views, search appearances
- Persona context (if present): target persona/role signals used in content quality and recruiter visibility proxy
**v1 data reality (current repo ingestion):**
- Profile snapshot fields + `followers_count`/`connections_count` are present.
- `activity` exists but is typically limited (date/title/url + sometimes likes/comments).
- Impressions/reach, profile views, and search appearances are typically not available in LinkedIn-first v1; treat as proxy/N.A.
### Outputs
- `brand_score_total` (0–100)
- `brand_score_components[]` each with:
- `key` (stable enum)
- `weight` (PRD)
- `score` (0–100)
- `weighted_points` (score × weight)
- `signals` (structured numeric strings/values for auditability)
- `explanation` (human-readable, objective/measurable)
- `insufficient_data` (boolean)
- `brand_score_history[]` trendline points (date, total score)
### Error + missing data behavior
Pick **one explicit policy** so scores don’t jitter unpredictably:
- **Recommended v1:** *weight renormalization*.
- If component lacks minimum data, mark `insufficient_data=true`.
- Exclude it from total weighting and renormalize remaining weights to sum to 1.0.
- Still return the missing component in breakdown (so UI can display “N/A”).
**v1 proxy rule:** if a PRD metric is not collectible on LinkedIn (e.g., impressions, profile views, search appearances), compute a deterministic proxy where possible; otherwise mark the component `insufficient_data=true` and rely on renormalization.
### Versioning
Persist a scoring version string (e.g. `2026-03-27.v1`) with each snapshot to support future formula tweaks.
---
## 2) Component model (PRD weights + measurable signals)
> Scores are 0–100. Each component must produce structured `signals` and a plain-language explanation referencing those signals.
### 2.1 Content Consistency — 20%
**Goal:** consistency of posting frequency + tone consistency over a rolling window.
**Signals (v1):**
- `posts_last_30d`
- `days_between_posts_avg`
- `days_between_posts_stddev`
- `weeks_meeting_target_cadence` (e.g., >=2 posts/week or configured target)
- `tone_label_distribution` (optional v1) or `tone_consistency_score`
**v1 note:** cadence is computable from `activity[].date` if present.
**Important v1 filter:** Bright Data “activity” often includes *interactions* (e.g., “Liked by X”), not just authored posts. Use `activity[].type` (populated from Bright Data `interaction` when available) to **exclude** non-authored items from cadence calculations.
Tone is optional and should be `insufficient_data=true` unless post text is available.
#### Tone consistency (addendum — v1.1 implementation checklist)
We currently score **cadence** deterministically from activity dates; this addendum covers adding the optional “tone consistency” portion without destabilizing totals.
- [ ] Extend ingestion/normalization so activity items include **authored post text** when available
- [ ] Prefer a stable field name in the normalized profile dict (e.g. `activity[].text`).
- [ ] Ensure non-authored items are filtered (continue using `activity[].type/interaction` heuristics).
- [ ] Add tone classification (deterministic-first, AI optional)
- [ ] Add adapter method (or reuse existing AI adapter) to classify tone labels for a post: e.g. `{tone: "educational"|"opinion"|"personal"|"promo"|...}`
- [ ] Cache tone labels per post URL/id when possible to avoid re-scoring.
- [ ] Compute tone consistency signals
- [ ] `tone_label_distribution` (counts per label)
- [ ] `tone_entropy` (or similar) as the numeric signal
- [ ] `tone_consistency_score` mapped 0–100
- [ ] If fewer than N posts have text (suggest N=3), mark tone as `insufficient_data=true`.
- [ ] Merge cadence + tone into the existing `content_consistency` score
- [ ] Recommended split: cadence 70%, tone 30% (configurable)
- [ ] Keep overall component deterministic when AI is unavailable:
- [ ] If AI tone classification fails, fall back to `insufficient_data=true` for tone and score cadence only.
- [ ] Update explanation string to include tone when scored
- [ ] Example: “Cadence variance is X days; tone consistency is Y/100 across N posts.”
- [ ] Add tests
- [ ] Test cadence-only path remains unchanged.
- [ ] Test tone-scored path produces stable distribution + score.
**Computation idea (v1 deterministic + optional AI):**
- Frequency score derived from cadence stability:
- reward meeting minimum weekly target
- penalize high variance in gaps
- Tone consistency (optional): classify each post tone (adapter AI), compute entropy / variance; map to 0–100.
**Explanation template:**
- “You posted {posts_last_30d} times in 30 days. Cadence variance is {stddev} days; {weeks_meeting_target_cadence}/4 weeks meet target.”
### 2.2 Audience Growth — 15%
**Goal:** follower/connection growth rate over last 30 days.
**Signals:**
- `followers_start_30d`
- `followers_end_30d`
- `follower_growth_abs`
- `follower_growth_rate`
**v1 requirement:** this component needs historical follower counts. Implement via metrics snapshots (see §3.3).
**Computation (v1):**
- `growth_rate = (end - start) / max(start, 1)`
- Map to 0–100 using bands (configurable), e.g.:
- <=0% → 10
- 0–1% → 40
- 1–3% → 70
- 3–6% → 85
- >6% → 95
**Explanation:**
- “Followers changed from {start} → {end} (+{abs}, {rate:.1%}) in 30 days; target band is {target_band}.”
### 2.3 Engagement Rate — 15%
**Goal:** likes/comments/shares/saves as % of reach.
**Signals:**
- `posts_counted`
- `impressions_total` (or `reach_total` if you have it)
- `engagements_total`
- `engagement_rate_weighted_avg`
- `engagement_rate_p50` / `p75` (optional)
**Computation (v1):**
- Prefer weighted average: `sum(engagements)/sum(impressions)` across posts with impressions.
- Guardrails for low denom.
**v1 LinkedIn-first proxy:** if impressions/reach aren’t available, use a deterministic proxy:
- `engagements_total = sum(likes + comments)` across recent activity items with those fields
- `engagements_per_post_avg = engagements_total / max(posts_counted, 1)`
- Optional normalization: `engagements_per_post_per_1k_followers`
If engagement signals are missing/too sparse, mark `insufficient_data=true` and renormalize.
**Important v1 filter:** only count *authored posts* (exclude “liked/commented/shared by user” items based on `activity[].type`).
**Explanation:**
- “Avg engagement rate is {rate:.2%} across {posts_counted} posts; benchmark target band {target_band}.”
### 2.4 Recruiter Visibility — 15%
**Goal:** profile appearance rate in recruiter/talent searches.
**Signals (best effort LinkedIn-first):**
- Preferred if collectible:
- `search_appearances_7d`, `search_appearances_30d`
- `profile_views_7d`, `profile_views_30d`
- Proxy if not collectible:
- `keyword_coverage_score` (0–100)
- `missing_keywords_count`
**Computation (v1):**
- If search appearances/profile views available: score by percentile bands.
- Else: compute proxy keyword coverage vs target role/persona keyword set.
**v1 default:** treat search appearances/profile views as unavailable and use keyword coverage proxy.
**Explanation:**
- “Search appearances (7d): {x}. Keyword coverage {coverage}/100; missing {n} high-signal recruiter keywords.”
### 2.5 Content Quality — 10%
**Goal:** AI-assessed relevance/originality/alignment to persona framework.
**Signals:**
- `ai_quality_score_avg`
- `ai_relevance_avg` (optional detail)
- `ai_originality_avg` (optional detail)
- `ai_persona_alignment_avg` (optional detail)
- `low_quality_flags` (e.g., repetitive template, too short)
**Computation (v1):**
- Use AI adapter to score posts (sample last N posts).
- Average to 0–100; apply small penalties for repeated/low-effort patterns.
**v1 fallback:** if post text isn’t collectible, score using profile text (headline + summary + recent roles) and reflect that in `signals`.
**Explanation:**
- “Quality score {score}/100 across {n} posts: relevance {r}/100, originality {o}/100, persona alignment {p}/100.”
### 2.6 Cross-Platform Consistency — 5% (schema-ready)
**Goal:** messaging/voice alignment across connected platforms.
**Signals:**
- `connected_platforms`
- `platforms_scored`
- `cross_platform_similarity` (if >1 platform)
**Computation (v1):**
- If only LinkedIn connected: mark `insufficient_data=true` and exclude via renormalization.
- If multiple: compare embedding similarity of bios/headlines + recent posts themes.
**Explanation:**
- “Only LinkedIn connected; cross-platform consistency isn’t scored yet.”
---
## 3) Persistence + history (trend line)
### 3.1 Storage model
Add a snapshot table to store history and component breakdown for auditability.
**Recommended table:** `brand_score_snapshots`
- `id`
- `user_id`
- `platform` (string/enum)
- `window_start`, `window_end`
- `score_total` (numeric)
- `components_json` (JSON/JSONB)
- `scoring_version` (string)
- `created_at`
Also store a “latest” summary on the user profile (fast reads):
- `latest_brand_score_total`
- `latest_brand_score_components_json`
- `latest_brand_score_calculated_at`
### 3.2 Trendline
- Provide daily points (last N days).
- If multiple snapshots/day: use latest per day (simple, stable).
### 3.3 Metrics snapshots (Audience Growth input)
Add a small metrics history table so we can compute change-over-time deterministically.
**Recommended table:** `social_metrics_snapshots`
- `id`
- `user_id`
- `platform` (string/enum; v1 = `linkedin`)
- `followers_count` (int nullable)
- `connections_count` (int nullable)
- `captured_at` (timestamp)
**Capture policy (v1):**
- Insert a snapshot after each successful LinkedIn sync.
- Optional dedupe: keep at most 1 snapshot/day per user+platform.
---
## 4) Engine design (code structure)
### 4.1 Orchestrator
In `app/engine/brand_score_engine.py` (or equivalent), implement:
- `compute_brand_score(user_id, platform, window_days=30) -> BrandScoreResult`
- `compute_and_persist_brand_score(...) -> BrandScoreSnapshot`
### 4.2 Component functions
Create pure functions (easy to test):
- `score_content_consistency(data) -> ComponentResult`
- `score_audience_growth(data) -> ComponentResult`
- `score_engagement_rate(data) -> ComponentResult`
- `score_recruiter_visibility(data) -> ComponentResult`
- `score_content_quality(data) -> ComponentResult`
- `score_cross_platform_consistency(data) -> ComponentResult`
### 4.3 Config + thresholds
Centralize mapping bands and targets so they’re easy to tune:
- engagement target bands
- growth target bands
- cadence target
- minimum sample sizes (e.g., >=3 posts for some components)
---
## 5) API changes
### 5.1 Endpoints (or dashboard response augmentation)
Add (or extend existing dashboard routes):
1) `GET /brand-score/latest`
- Returns: total + components breakdown + `scoring_version` + `window`.
2) `GET /brand-score/trend?days=30`
- Returns: array of `{date, score_total}` (optionally include component totals later).
### 5.2 Response schemas
Add Pydantic models:
- `BrandScoreComponentBreakdown`
- `BrandScoreSnapshotResponse`
- `BrandScoreTrendResponse`
---
## 6) Triggering computations
Compute snapshot when inputs change.
**Recommended triggers:**
- After each LinkedIn sync job completes
- Optionally: nightly job (backup) to ensure fresh trendline
Implementation:
- Hook into job dispatcher / sync pipeline to call `compute_and_persist_brand_score(user_id, "linkedin")`.
**v1 addition:** after LinkedIn sync, also write a `social_metrics_snapshots` row (followers/connections) so Audience Growth works.
---
## 7) Tests (minimum set)
Add unit tests in `tests/` to stabilize the scoring math:
1) `test_brand_score_components_happy_path`
- Fake dataset with posts + metrics
- Assert all 6 component keys present
- Assert weights consistent with PRD
- Assert total score within expected range and deterministic
2) `test_brand_score_missing_data_renormalization`
- Omit recruiter visibility metrics & cross-platform
- Assert `insufficient_data` flagged
- Assert total calculation uses renormalized weights
3) `test_brand_score_snapshot_persistence`
- Compute-and-persist writes a snapshot and updates “latest” summary
4) `test_metrics_snapshots_enable_audience_growth`
- Insert two snapshots in-window
- Assert the Audience Growth component derives start/end from snapshots
---
## 8) Execution steps (recommended order)
1) Add DB migration(s): `brand_score_snapshots` + latest fields on user summary
1b) Add DB migration: `social_metrics_snapshots`
2) Implement/extend scoring engine with component spec + versioning
3) Add repository methods for snapshot insert + trend queries
4) Add API endpoints/schemas for latest + trendline
5) Wire compute trigger after sync
6) Add tests and make them green
---
## 9) Notes / open questions (non-blocking)
- Recruiter visibility and search appearances might be hard to collect reliably without LinkedIn API access; v1 can use a proxy keyword coverage score.
- Engagement rate impressions/reach may be unavailable on LinkedIn-first v1; use engagement proxies or mark insufficient data + renormalize.
- Cross-platform consistency should be marked “insufficient data” unless multiple platforms are connected.
- If you already have brand score summary fields on `social_users`, reuse them; otherwise add them.
@@ -0,0 +1,244 @@
# Implementation Plan: Brand Score → RQx mapping + events
Date: 2026-03-27
Scope source: `docs/prd-additions.md` → section **“2) Brand Score → RQx mapping + events”**
Out of scope: Posting/automation to platforms; adding new platforms.
---
## ✅ Requirements checklist (PRD)
- [ ] Compute & store **RQx contribution** as: `Brand Score × 0.4`
- [ ] Ensure recalculation runs **after every platform sync** (LinkedIn daily in PRD)
- [ ] Emit update when Brand Score changes by **5+ points**, including before/after + metadata
- [ ] Persist and expose Brand Score history for the dashboard (trendline)
---
## Tiny contract (what we’ll build)
### Inputs
- `social_user_id`
- `platform` (initially `linkedin`)
- `sync_context` metadata (at least: `sync_job_id`, `sync_started_at`, `sync_finished_at`, `source`)
### Computation
- `brand_score` (0–100) from the active Brand Score engine (v1/v2)
- `rqx_contribution = brand_score * 0.4`
### Outputs
1. **Current state** persisted on the user (fast reads):
- `brand_score` (and updated timestamp)
- `rqx_contribution` (and updated timestamp)
2. **History** row appended each recompute:
- before/after values + delta
- metadata (platform, sync_job_id, engine version)
3. **Event** emitted only when threshold met:
- condition: `abs(brand_score_after - brand_score_before) >= 5`
### Error handling expectations
- If sync fails → do not recompute.
- If recompute fails → do not partially write; record failure on the job.
- If no previous score exists → write current + history. Event emission rule should be explicit (see Decisions).
---
## Decisions to lock (before coding)
1. **Engine selection**
- Confirm whether `app/engine/brand_score_engine.py` or `app/engine/brand_score_v2_engine.py` is source-of-truth.
- Add `engine_version` to history metadata to prevent confusion during the migration period.
2. **First-time score event behavior**
- Option A: Do **not** emit threshold event on first compute.
- Option B: Emit `brand_score.initialized` as a separate event.
3. **Event delivery mechanism**
- If an existing emitter/bus exists (e.g., `app/adk/emitter.py`), reuse it.
- If not, implement an **Outbox table** (durable events) and a worker to dispatch them.
---
## Prerequisites / gaps to confirm in the repo
These should be verified before implementation to avoid rework:
1. **Where Brand Score and summaries are stored today**
- Migrations suggest existing work (e.g., brand score “summary”, “snapshots”, “metrics history”). Confirm actual tables/columns.
2. **Platform sync lifecycle hook**
- Identify the “sync completed successfully” point for LinkedIn.
- We need a stable callback/hook to trigger recomputation exactly once per successful sync.
3. **Existing RQx representation**
- There are tests around the RQx formula. Confirm whether there’s a persisted “RQx” score model already.
- Ensure `rqx_contribution` doesn’t conflict with other RQx computation.
4. **History API shape already used by dashboard**
- If the dashboard already expects a trendline format, align the endpoint/DTO to it.
---
## Data model plan
### A) Current state (likely on `social_users`)
Add/confirm:
- `brand_score` (numeric)
- `rqx_contribution` (numeric)
- `brand_score_updated_at` (datetime)
Indexes:
- `(id)` already
- optional: `(brand_score_updated_at)` if filtered queries are common
### B) History table (trendline + auditing)
If not already present, add a new table like `brand_score_history`:
- `id` (pk)
- `social_user_id` (fk)
- `platform` (string)
- `brand_score_before` (numeric, nullable)
- `brand_score_after` (numeric, not null)
- `rqx_before` (numeric, nullable)
- `rqx_after` (numeric, not null)
- `delta` (numeric, not null)
- `sync_job_id` (string/uuid, nullable)
- `engine_version` (string)
- `metadata` (json)
- `created_at` (datetime)
Indexes:
- `(social_user_id, created_at desc)`
- `(platform, created_at desc)` (optional)
Note: If an existing “snapshots/history” table already exists, extend it rather than duplicating.
---
## Service boundary (single place to do it right)
Create/extend a service method:
`BrandScoreService.recalculate_and_persist(social_user_id, platform, sync_context) -> RecalcResult`
Responsibilities:
1. Load current user and previous score.
2. Compute new `brand_score` via engine.
3. Compute `rqx_contribution = brand_score * 0.4`.
4. Persist updates:
- update current fields on user row
- append history row
5. Event threshold logic:
- if previous exists and `abs(delta) >= 5` → emit event
Why this matters:
- Keeps sync jobs thin.
- Prevents duplicated threshold logic across code paths.
---
## Wiring: recompute after every platform sync
### Where to hook
- Identify the LinkedIn sync job/task entrypoint (likely under `app/jobs/` or `app/services/`).
### When to hook
- **After** all sync writes are committed (so the score uses fresh data).
- Prefer same DB session/transaction boundaries when reasonable.
### Backfill / catch-up
- Add a safe admin-only/manual job to recompute for all users (optional, but useful after schema change).
---
## Event design
### Event name
- `brand_score.updated` (threshold-triggered)
- optional: `brand_score.initialized`
### Payload (PRD-required)
- `social_user_id`
- `platform`
- `brand_score_before`
- `brand_score_after`
- `brand_score_delta`
- `rqx_before`
- `rqx_after`
- `computed_at`
- `sync_job_id` (if present)
- `engine_version`
- `metadata` (freeform)
### Delivery
- If existing emitter exists: publish immediately.
- If not: write to durable outbox and dispatch asynchronously.
---
## API changes: trendline exposed for dashboard
### Endpoint (example)
- `GET /v1/social-users/{id}/brand-score/history?days=30`
### Response shape (example)
- `current`: `{ brand_score, rqx_contribution, updated_at }`
- `trendline`: array of `{ date, brand_score, rqx_contribution }`
Implementation notes:
- Ensure efficient paging/limit.
- Default window (e.g., 30 or 90 days).
---
## Tests (minimum set)
1. **Unit**: computes `rqx_contribution = brand_score * 0.4`
2. **Unit**: emits event only when `abs(delta) >= 5`
3. **Unit**: first-time compute behavior (explicit rule)
4. **Integration-ish**: “sync completed” triggers recalculation call
5. **API**: history endpoint returns ordered trendline
---
## Step-by-step execution plan
1. **Recon** (fast repo check)
- Confirm existing tables/migrations for brand score history/snapshots.
- Identify sync pipeline completion point.
- Identify current event/emitter infra.
2. **Schema**
- Add missing columns/tables for `rqx_contribution` + history fields.
- Add indexes.
3. **Service**
- Implement `recalculate_and_persist`.
- Add tests.
4. **Sync wiring**
- Call service after LinkedIn sync success.
- Add a test around the hook.
5. **Events**
- Implement emission (or outbox write).
- Test threshold behavior.
6. **API**
- Add/extend endpoint for trendline.
- Add tests.
7. **Backfill (optional but recommended)**
- Add a job/CLI path to compute and write missing `rqx_contribution` + history.
---
## Acceptance criteria
- On every successful LinkedIn sync, Brand Score recomputes and persists.
- `rqx_contribution` is always consistent with `brand_score * 0.4`.
- History shows a trendline (at least daily points or per-sync points).
- When Brand Score changes by **≥ 5**, an event is emitted with complete before/after + metadata.
- Unit tests cover formula + threshold + first-time behavior.
@@ -0,0 +1,340 @@
# Implementation Plan: LinkedIn Profile Rewrite Variants + User Selection (Draft-only)
Date: 2026-03-27
Source: `docs/PRD.md` → “5.1 LinkedIn → Profile Rewrite” + `docs/prd-additions.md` item (3)
Scope: **Generate → Approve (select)** only. **No posting**. **No LinkedIn write-back**.
---
## Goals (what we’re shipping)
Implement a draft-only workflow that:
1. Generates rewrite options for a user’s LinkedIn profile.
2. Returns them via API for review.
3. Lets the user select/approve one option per section.
4. Persists both the generated variants and the user’s selection for dashboard/UI use.
PRD-required deliverables:
- Headline: **3 variants**
- About section: optimized version(s)
- Experience bullet points: per role
- Skills list: mapped to **Q-Score signals** + **target JD keywords**
---
## Non-goals / explicit exclusions
- No automated posting.
- No executing edits on LinkedIn.
- No new platform support (LinkedIn-first only).
---
## Existing repo capabilities we will reuse
### AI generation primitives (already present)
The repo already includes AI adapter functions in `app/adapters/ai_service.py`:
- `improve_headline(profile_data, current_headline, context)`
- `improve_summary(profile_data, current_summary, context)`
- `improve_experience(profile_data, experience_entry, context)`
- `suggest_skills(profile_data, context)`
These are thin wrappers over `BrandingIntelligenceEngine` and should be reused to avoid duplicating prompt logic.
### LinkedIn profile persistence (already present)
`app/services/brand_score_recalc_service.py` demonstrates that a user has persisted LinkedIn profiles via `user.linkedin_profiles` and that a “latest profile” can be selected using `last_synced_at`.
### Approval workflow patterns (already present)
There’s a state-machine style approval flow in `app/use_cases/approval_workflow.py` (currently for content drafts). We’ll mirror the same ergonomics for rewrite drafts (approve/reject), but with rewrite-specific statuses.
---
## Proposed domain contract (inputs/outputs)
### Inputs (Generate)
- `social_user_id` (derived from auth)
- `platform` = `linkedin`
- `target_role`: string (e.g., “Senior Product Manager”)
- `target_jd_keywords`: list[string] (0..N)
- Optional: `context` string (free-form notes like “targeting fintech”, “prefer concise voice”)
### Output (Draft)
A persisted “rewrite draft” with generated variants and stable IDs so frontends can select precisely.
Recommended structure (API DTO / Pydantic schema):
- `id` (uuid)
- `user_id`
- `platform`
- `source_linkedin_profile_id` (the snapshot/profile record used)
- `inputs`: `{ target_role, target_jd_keywords, context }`
- `generated`:
- `headline_variants`: **3** items
- `about_variants`: 1–2 items (recommend 2 for user choice)
- `experience_variants`: per role, list of bullet options
- `skills_suggestions`: ranked list with provenance tags
- `selection` (nullable until approved)
- `status`: `draft | approved | rejected | superseded`
- `created_at`, `updated_at`
---
## Data model & migrations
### Option A (recommended): JSON-first, minimal tables
Add a new table, e.g. `linkedin_profile_rewrites`:
- `id` UUID PK
- `user_id` FK (or clerk id; follow existing account conventions)
- `platform` string (`linkedin`)
- `source_linkedin_profile_id` FK (or just store the profile UUID)
- `inputs_json` JSONB
- `generated_json` JSONB
- `selection_json` JSONB nullable
- `status` enum/string
- `created_at` / `updated_at`
Pros: fastest to ship; easy to evolve.
### Option B: normalized variants tables
Add separate tables for variants and selections. Pros: easier analytics/search. Cons: more work now.
**Decision:** Start with Option A unless we have an immediate need to query variants independently.
### Migration tasks
- Create Alembic revision adding the new table and any needed enums.
- Indexes:
- `(user_id, created_at desc)`
- `(user_id, status)`
---
## Generation service design
Add a new service, e.g. `app/services/linkedin_profile_rewrite_service.py`.
### Responsibilities
1. Load the **latest** connected LinkedIn profile for the user (same selection logic as BrandScore recalc).
2. Build `profile_data` dict in the same shape used by AI functions.
3. Generate variants:
#### Headline: 3 variants
Implementation approach:
- Use `ai_service.improve_headline` three times with different context “styles” to force diversity:
1) Recruiter/keyword-forward
2) Achievement/value-prop
3) Persona/voice-forward
Additionally, enforce:
- Char limit validation (keep within a safe LinkedIn headline cap)
- Deduplication (variants must be meaningfully different)
#### About: optimized version(s)
- Use `ai_service.improve_summary` to produce 2 variants (recommended).
- Enforce max length.
#### Experience bullets: per role
- For each experience entry, call `ai_service.improve_experience` with:
- target role
- keywords
- “prefer metrics and outcomes”
Store results per role.
#### Skills: mapped to Q-Scores + JD keywords
- Use `ai_service.suggest_skills` with context containing:
- target role
- JD keywords
- any available Q-score signals (see prerequisites below)
Then post-process:
- Normalize (title case / trim)
- Deduplicate
- Tag provenance for each skill: `jd | qscore | both | ai`
### Explainability (“objective signals”)
While PRD item (3) doesn’t explicitly demand explanations, it helps UX and aligns with later recruiter visibility features.
For each variant include:
- `keywords_used`: list[string]
- `rationale`: short string
---
## API endpoints
Add routes under `app/api/v1/` (exact module placement should match existing routing conventions).
### 1) Generate
`POST /v1/linkedin/profile-rewrite/generate`
Body:
- `target_role`
- `target_jd_keywords`
- optional `context`
Response: rewrite draft DTO.
### 2) Fetch by id
`GET /v1/linkedin/profile-rewrite/{rewrite_id}`
Response: rewrite draft DTO.
### 3) Approve / select
`POST /v1/linkedin/profile-rewrite/{rewrite_id}/approve`
Body (selection contract):
- `headline_variant_index` or `headline_variant_id`
- `about_variant_index` or `about_variant_id`
- `experience_selection`: per role key, chosen bullets (either “accept all” or per-bullet indices)
- `skills_final`: final list of skills the user wants
Response: updated rewrite DTO with `status=approved` and non-null `selection`.
### 4) Reject (optional but recommended)
`POST /v1/linkedin/profile-rewrite/{rewrite_id}/reject`
Body: optional feedback.
---
## State machine & lifecycle
Suggested statuses:
- `draft`: generated, awaiting decision
- `approved`: selection persisted
- `rejected`: user rejected (optional feedback)
- `superseded`: a newer draft was generated; keep old one for audit
Rules:
- Approve only allowed from `draft`.
- Reject only allowed from `draft`.
- Generating a new draft can mark older `draft` rows as `superseded` (optional, but recommended).
---
## Prerequisites / missing pieces to confirm before implementing
These are required to meet PRD item (3) fully and cleanly:
1) **Target role + JD keywords source**
- We need a stable way to supply `target_role` and `target_jd_keywords`.
- If not already stored in DB, we’ll require the frontend/client to pass them in the `generate` request.
2) **Q-Score signals beyond a single number**
- “Skills list mapped to Q-Scores” implies we can access either:
- a Q-score category breakdown, or
- a list of validated skill signals.
- If current Q-score integration only returns an aggregate score, we should define a short-term mapping strategy:
- Treat JD keywords as first-class and tag them as `jd`
- Tag AI-suggested skills as `ai`
- Add `qscore` provenance only when breakdown is available
3) **Stable LinkedIn profile snapshot identifier**
- We can already pick the latest `linkedin_profiles` record.
- We must ensure we can store a stable `source_linkedin_profile_id` (UUID) and that it’s available in the model.
4) **AI engine supports deterministic structured outputs**
- Current AI functions return `dict`. We’ll need to validate:
- headline output includes a `headline` string (or similar)
- summary output includes `summary`
- experience output includes bullet list
- skills output includes list
- If current outputs are not structured enough, we’ll add a thin normalization layer in the rewrite service.
---
## Testing plan
Add tests in `tests/` that cover:
1) **Variant counts & structure**
- Generate returns exactly **3** headline variants.
- About returns at least 1 variant (prefer 2).
- Experience returns entries for each role.
2) **Approval persistence**
- Approve endpoint updates status to `approved`.
- Selection is persisted and returned.
3) **Ownership / auth**
- Users can’t approve/fetch drafts they don’t own.
4) **Idempotency & superseding**
- Generating multiple drafts for the same user creates multiple rows.
- (If implemented) older drafts become `superseded`.
---
## Rollout notes
- Keep endpoints feature-flagged if you already have feature flag patterns.
- Log generation/approval events without leaking secrets.
---
## Acceptance criteria
1) **Generate**
- Given a user with a connected LinkedIn profile, calling `POST /v1/linkedin/profile-rewrite/generate` returns a persisted draft containing:
- Headline variants: **exactly 3**
- About variants: **>= 1**
- Experience rewrites: per experience role in the source profile
- Skills suggestions: list includes JD keywords when provided
2) **Approve (user selection)**
- Calling `POST /v1/linkedin/profile-rewrite/{id}/approve` persists the selection and changes status to `approved`.
- Approved draft is retrievable via `GET /v1/linkedin/profile-rewrite/{id}` with `selection` populated.
3) **Draft-only**
- No endpoint performs posting or any platform write-back.
- No code path attempts to modify LinkedIn.
4) **Security/ownership**
- Users cannot access or approve drafts they don’t own.
5) **Regression safety**
- Existing content approval workflow is unaffected.
- Existing Brand Score recalculation remains unaffected.
---
## Final implementation checklist
- [ ] Confirm where/how `target_role` and `target_jd_keywords` will be sourced (request-only vs persisted profile settings).
- [ ] Confirm Q-score breakdown availability; decide interim provenance strategy if breakdown is missing.
- [ ] Add DB model(s) + Alembic migration for rewrite drafts.
- [ ] Add Pydantic schemas for request/response.
- [ ] Implement `linkedin_profile_rewrite_service` using `app/adapters/ai_service.py` primitives.
- [ ] Add API routes: generate, get by id, approve (and optional reject).
- [ ] Add tests for generation counts, approval persistence, ownership.
- [ ] Run unit tests and ensure green.
@@ -0,0 +1,184 @@
# Execution playbook — migrate Brand Score v1 → v2 (component model)
Date: 2026-03-27
Scope: replace audit-derived Brand Score v1 with PRD component-model Brand Score v2.
This doc is the **step-by-step execution order** for completing the migration, plus a **final verification checklist** to confirm nothing still depends on v1.
---
## What we’re migrating (quick references)
### v1 (legacy)
- **Engine:** `app/engine/brand_score_engine.py`
- Function: `compute(audit, extra_profile_data)`
- Output: `BrandScoreResult` with `brand_score`, `grade`, `strengths`, `quick_wins`
### v2 (target)
- **Engine:** `app/engine/brand_score_v2_engine.py`
- Function: `compute_v2(inputs, ...)`
- Output: `BrandScoreV2Result` with `brand_score_total`, `components[]`, `scoring_version`, `window_*`
- **Persistence wrapper (canonical path):** `app/services/brand_score_service.py::compute_and_persist_v2()`
- Writes:
- Snapshot row (`brand_score_snapshots`)
- Cache (`social_users.brand_score_summary` + `social_users.brand_score_latest_calculated_at`)
### Known v1 call sites to remove
- `app/use_cases/run_profile_analysis.py`
- Imports and calls v1 compute: `from app.engine.brand_score_engine import compute as compute_brand_score`
- Writes legacy cache keys: `{"brand_score": ..., "brand_grade": ...}`
- `app/use_cases/recalculate_brand_score.py`
- Imports and calls v1 compute
- Writes legacy cache keys
- `app/use_cases/get_analysis_view.py`
- References `BrandScoreEngine` (verify if legacy/unused and migrate/remove)
### Confirmed v1 usage map (what actually hits prod today)
This section is here so we can migrate with confidence and not miss a real runtime path.
#### Production (API)
- `POST /api/v1/analysis/profile/{profile_id}`
- Route: `app/api/v1/analysis.py::run_profile_analysis`
- Use-case: `app/use_cases/run_profile_analysis.py::execute`
- v1 call: inside `execute()`
- `from app.engine.brand_score_engine import compute as compute_brand_score`
- `brand = compute_brand_score(audit, profile_data)`
#### Production (worker job)
- Job type: `recalculate_brand_score`
- Dispatcher: `app/jobs/dispatcher.py::_handle_recalculate_brand_score`
- Use-case: `app/use_cases/recalculate_brand_score.py::execute`
- v1 call: `brand = compute_brand_score(audit, profile_data)`
#### Non-production (tests/docs)
- Tests: `tests/test_engine.py`
- Imports v1 compute and asserts legacy outputs.
- Must be migrated/removed before deleting v1.
- Docs: multiple references (this plan + architecture docs)
- No runtime impact, but keep in sync with the final migration state.
---
## Execution order ✅
### 0) Pre-flight (don’t change behavior yet)
- [ ] Confirm DB migrations are present and applied in dev:
- [ ] `brand_score_snapshots`
- [ ] `social_metrics_snapshots`
- [ ] `social_users.brand_score_latest_calculated_at`
- [ ] Confirm v2 endpoints work without v1:
- [ ] `GET /brand-score/latest`
- [ ] `GET /brand-score/trend`
### 1) Introduce a migration flag (safe rollout)
- [ ] Add a settings/env flag (example name): `BRAND_SCORE_VERSION=v2|v1`
- [ ] Default should be **v2**.
- [ ] Keep `v1` only as a temporary fallback.
### 2) Stop writing the legacy v1 cache shape (make v2 the source of truth)
Target: only v2 writes the **canonical** cache and snapshot.
- [ ] Update `app/use_cases/run_profile_analysis.py`
- [ ] Remove/avoid calling `app.engine.brand_score_engine.compute()`
- [ ] Ensure it calls **only** `compute_and_persist_v2()` when the flag is v2
- [ ] Ensure `social_users.brand_score_summary` is not overwritten with legacy keys after v2 runs
- [ ] Update `app/use_cases/recalculate_brand_score.py`
- [ ] Remove/avoid calling v1 compute
- [ ] Return brand score value from v2 total (`brand_score_total`) for any callers expecting a number
- [ ] Ensure it doesn’t overwrite the v2 cache shape
- [ ] Update/verify `app/use_cases/get_analysis_view.py`
- [ ] If it still reads v1 fields (`brand_score`, `grade`), migrate it to read v2 cache
- [ ] If it constructs or uses a separate BrandScoreEngine abstraction, collapse it onto v2
### 3) Update eventing/delta logic to compare v2 totals
If you emit `brand_score.delta` events anywhere:
- [ ] Change “before/after” comparison to use:
- `brand_score_total` (v2) from `social_users.brand_score_summary`
- [ ] Keep the same delta threshold (>= 5) unless PRD says otherwise
- [ ] Ensure the computed `rqx_contribution` continues to be derived from the total (v2 total)
### 4) Ensure metrics snapshots are captured (audience growth + trend stability)
- [ ] Verify the capture points write `social_metrics_snapshots` after successful sync/import:
- [ ] `app/use_cases/sync_profile.py`
- [ ] `app/use_cases/import_profile.py`
- [ ] Ensure the “at most 1/day per user+platform” behavior remains (via `MetricsRepo.upsert_daily_snapshot()`)
### 5) Backfill (optional but recommended)
- [ ] Create a one-off job/script to backfill:
- [ ] `social_metrics_snapshots` (if missing)
- [ ] `brand_score_snapshots` for last N days (or at least one snapshot per user)
- [ ] Confirm `GET /brand-score/trend` returns non-empty results for existing users
### 6) Remove v1 usage (+ cleanup)
- [ ] Remove imports/usages of v1 engine from all use-cases
- [ ] Remove “legacy” writes to `social_users.brand_score_summary` keys:
- `brand_score`, `brand_grade`
- [ ] Keep `GET /brand-score/latest` backward-compatible temporarily (it already supports both shapes)
- [ ] After a confidence window:
- [ ] Delete or archive `app/engine/brand_score_engine.py`
- [ ] Remove v1 compatibility branch from `app/use_cases/get_brand_score_latest.py`
### 7) Verify wiring is clean (routes + worker)
These are the two *runtime* paths that must be free of v1 imports before you can claim the migration is complete.
- [ ] API path no longer imports v1:
- [ ] `app/use_cases/run_profile_analysis.py` has no `brand_score_engine` import
- [ ] `POST /api/v1/analysis/profile/{profile_id}` still succeeds and returns expected payload
- [ ] Worker path no longer imports v1:
- [ ] `app/use_cases/recalculate_brand_score.py` has no `brand_score_engine` import
- [ ] `JobType.RECALCULATE_BRAND_SCORE` job still succeeds and updates v2 snapshot + cache
---
## Final verification checklist (done = migration complete)
### Codebase checks
- [ ] No remaining imports of v1 engine:
- [ ] `from app.engine.brand_score_engine import ...` is gone
- [ ] No references to `BrandScoreEngine` remain unless it’s v2-only
- [ ] `social_users.brand_score_summary` is written in **exactly one place**:
- [ ] `app/services/brand_score_service.py::compute_and_persist_v2()`
- [ ] No code overwrites `brand_score_summary` with legacy keys after v2 runs
### Data shape checks (runtime)
- [ ] `GET /brand-score/latest` returns:
- [ ] `brand_score_total`
- [ ] `components[]` (6 keys)
- [ ] `scoring_version`
- [ ] `window`
- [ ] `GET /brand-score/trend` returns daily points from `brand_score_snapshots`
- [ ] New sync/import actions write `social_metrics_snapshots` for the user
### Scoring behavior checks
- [ ] Renormalization is stable when a component lacks data:
- [ ] missing components are marked `insufficient_data=true`
- [ ] total score remains in 0–100 and doesn’t crash
### Test coverage checks
- [ ] Unit tests cover v2 engine (already present)
- [ ] Add/green: persistence + cache update tests:
- [ ] snapshot row created
- [ ] cache updated in v2 shape
- [ ] Add/green: delta/eventing compares v2 totals
File diff suppressed because it is too large Load Diff
+158
View File
@@ -0,0 +1,158 @@
# Social Branding Service Plan
## Mission
Build a LinkedIn-first, individual-only social branding MVP that can ingest profile data, compute Brand Score v1, generate persona-based strategy and content plans, manage an approval queue, and expose progress back to the GrowQR ecosystem.
## Locked Scope
### In scope tonight
- individual service only
- LinkedIn-first profile import/audit flow
- manual import or adapter fallback instead of hard OAuth dependency
- persona selection from pathway goal/archetype context
- Brand Score v1
- content calendar generation
- draft generation for LinkedIn
- approval queue
- posting state machine and manual/export-based publish completion
- dashboard and analytics snapshots
- event emission for Brand Score updates
### Explicitly deferred
- enterprise social branding
- Instagram, X, YouTube runtime support
- real auto-posting to live platforms
- full engagement automation
- recruiter search visibility integrations that depend on unstable APIs
- multi-platform analytics parity
## Implementation Decisions
- LinkedIn is the only live platform for v1. Other platforms remain schema-ready only.
- Support manual JSON/import or URL-backed profile snapshot ingestion so the service is not blocked on OAuth.
- Brand Score v1 must normalize to available LinkedIn data. Do not block on missing cross-platform metrics.
- The publish step may be manual-confirmed: generate final copy and mark it posted when the user confirms it was used.
- Use deterministic templates for the 80% toolkit and limited AI generation only for the 20% dynamic layer.
## Domain Model
- `social_accounts`
- `social_profile_snapshots`
- `brand_scores`
- `persona_strategies`
- `content_calendar_items`
- `content_drafts`
- `approval_queue`
- `analytics_snapshots`
- `engagement_drafts`
## Core APIs
- `POST /api/v1/accounts/linkedin/import`
- `POST /api/v1/persona/select`
- `POST /api/v1/brand-score/recalculate`
- `GET /api/v1/dashboard/{user_id}`
- `POST /api/v1/content-calendar/generate`
- `GET /api/v1/content-calendar/{user_id}`
- `POST /api/v1/drafts/generate`
- `POST /api/v1/approval-queue/{item_id}/approve`
- `POST /api/v1/approval-queue/{item_id}/reject`
- `POST /api/v1/approval-queue/{item_id}/regenerate`
- `POST /api/v1/publish/{item_id}/confirm`
## Pathways Dependency
Consume or mock:
- target role
- pathway milestone events
- goal/persona hints
- optional archetype
- optional Q-score changes
Use those to select persona strategy and dynamic content triggers.
## Phase Plan
### Phase 1: Foundations
- [x] Replace template domain with social profile/content models
- [x] Define persona enums and LinkedIn-only account type
- [x] Add Brand Score component model
- [x] Seed persona templates and content toolkit examples
### Phase 2: Profile Ingest And Audit
- [x] Implement LinkedIn import endpoint with manual payload fallback
- [x] Build profile audit scoring
- [x] Persist profile recommendations
- [x] Add tests for audit output
### Phase 3: Brand Score And Persona Strategy
- [x] Compute Brand Score v1 from available LinkedIn metrics
- [x] Map pathway context to one of the personas
- [x] Persist strategy settings and posting cadence
- [x] Emit Brand Score update event on meaningful changes
### Phase 4: Content Calendar And Drafts
- [x] Generate a 2-week content calendar
- [x] Enforce 80/20 split
- [x] Generate LinkedIn draft content
- [x] Add dynamic triggers for pathway milestone and score-change events
### Phase 5: Approval And Publish Flow
- [x] Implement approval queue
- [x] Support approve/reject/regenerate
- [x] Support manual publish confirmation
- [x] Record posting state transitions
### Phase 6: Dashboard And Hardening
- [x] Build dashboard endpoint with score trend and queue summary
- [x] Add happy path tests
- [x] Document deferred multi-platform and enterprise work
## Review Checklist
- [x] Service works without live LinkedIn OAuth
- [x] LinkedIn is the only live platform in v1
- [x] Brand Score does not depend on unavailable metrics
- [x] Approval queue blocks publishing until approval
- [x] Multi-platform and enterprise features stay deferred
- [x] Dashboard exposes useful state to Pathways and users
## Suggested File Layout
- `app/api/v1/accounts.py`
- `app/api/v1/content.py`
- `app/api/v1/dashboard.py`
- `app/services/brand_score.py`
- `app/services/persona_strategy.py`
- `app/services/content_calendar.py`
- `tests/test_social_branding_flow.py`
## Commands
```bash
cp .env.example .env
uv sync --extra dev
uv run pytest
docker compose up --build
```
## Done Definition
This service is done for the overnight run when a seeded user can:
1. import a LinkedIn profile snapshot
2. receive an audit and Brand Score
3. get a persona-based 2-week content calendar
4. review generated drafts in an approval queue
5. approve and mark a post as published
6. see score and activity reflected in a dashboard response
+161
View File
@@ -0,0 +1,161 @@
# PRD additions we can implement next (excluding posting + new platforms)
Date: 2026-03-26
Source: `docs/PRD.md`
Exclusions: **(1)** anything that actually posts/executes content to platforms, **(2)** adding/supporting new platforms beyond the current LinkedIn-first implementation.
This doc lists **only** items explicitly described in the PRD that are either missing or only partially implemented in the current codebase.
---
## 1) Brand Score: full PRD component model + breakdown
**PRD section:** “2.1 Brand Score Components”, “2.2 Brand Score → RQx Integration”
Add the remaining Brand Score components as first-class computed fields (and store their breakdown), not just a profile audit-derived score:
- **Content Consistency (20%)**: consistency of posting frequency + tone consistency over a rolling window.
- **Audience Growth (15%)**: follower/connection growth rate over a 30-day rolling window.
- **Engagement Rate (15%)**: likes/comments/shares/saves as % of reach.
- **Recruiter Visibility (15%)**: profile appearance rate in recruiter/talent searches (LinkedIn primary).
- **Content Quality (10%)**: AI-assessed relevance/originality/alignment to persona framework.
- **Cross-Platform Consistency (5%)**: messaging/voice alignment across connected platforms (schema-ready; compute from available platforms only).
Also implement PRD-required outputs:
- Brand Score trend line (history storage + API response)
- “objective, measurable signals” explanations per component
---
## 2) Brand Score → RQx mapping + events
**PRD section:** “2.2 Brand Score → RQx Integration”
Implement/confirm the PRD mapping and event rules end-to-end:
- Compute & store **RQx contribution** as: `Brand Score × 0.4`
- Ensure recalculation runs **after every platform sync** (LinkedIn daily in PRD)
- Emit update when Brand Score changes by **5+ points** (PRD) and include before/after + metadata
- Persist and expose Brand Score history for the dashboard (trendline)
---
## 3) LinkedIn profile rewrite variants + user selection
**PRD section:** “5.1 LinkedIn → Profile Rewrite”
Generate PRD-specified rewrite options for user approval/selection:
- Headline: **3 variants**
- About section: optimized version(s)
- Experience bullet points (per role)
- Skills list mapped to Q-Scores + target JD keywords
This is PRD “Generate → Approve” behavior and does **not** require posting.
---
## 4) Recruiter visibility: keyword gap analysis + injection recommendations
**PRD section:** “5.1 LinkedIn → Recruiter Visibility”; Brand Score component “Recruiter Visibility”
Add the PRD keyword gap workflow:
- Identify missing recruiter-search terms for the target role
- Provide **where to inject** them (headline/about/experience/skills) and generate suggested rewrites
- Score each section 0–100 and benchmark vs target role (PRD says “benchmark against top 10% in same role”)
---
## 5) Connection strategy drafts (no sending)
**PRD section:** “5.1 LinkedIn → Connection Strategy”
Implement the PRD deliverables without executing any connection actions:
- Identify target connections (target companies, peers, hiring managers)
- Draft **personalized connection request notes** for approval
- Track acceptance-rate analytics inputs (data collection method TBD per PRD)
---
## 6) Engagement response drafts (approval-gated, no automation)
**PRD sections:** Pillar “Engagement”; “4.2 Dynamic Content → Engagement response drafts”; “5.1 LinkedIn → Engagement Automation”
Add draft generation (only) for:
- Replies to comments on user posts
- Comments on posts from target connections/industry leaders
All actions remain **draft-only** and **approval-gated**.
---
## 7) Content calendar generation rules enforcement (planning-side only)
**PRD section:** “4.3 Content Calendar Generation Rules”
Enforce the PRD rules in calendar generation + approval queue mechanics:
- **Rule 1: Frequency enforcement** + reminders 48hrs in advance if slots aren’t approved
- **Rule 2: Platform sequencing** (formatting/unique versions) — for now enforce at least “no naive duplication” within LinkedIn content types/templates
- **Rule 3: 80/20 split** maintained over a rolling 2-week window
- **Rule 4: No auto-post without approval** (keep; optionally support PRD “auto-approve mode” as opt-in *approval behavior*, not posting)
- **Rule 5: Tone lock** after first 2 weeks unless user resets
- **Rule 6: Sensitive topic filter**: risky content is flagged and not queued for approval
These changes are about **planning, generation, and approvals**, not posting.
---
## 8) Tracking + weekly analytics dashboard (read-only metrics)
**PRD section:** “1.1 The Automation Loop → Track”; “5.1 LinkedIn → Analytics Tracked”
Implement the PRD “Track” layer to collect, store, and display (no posting needed):
- Profile views (7-day, 30-day)
- Search appearances
- Post impressions
- Engagement rate per post
- Connection acceptance rate
- Follower growth
Add PRD-required presentation:
- Weekly analytics dashboard
- Brand Score trend line
---
## 9) Data requirements layer (LinkedIn) — collection mechanism + constraints doc
**PRD section:** “6. Data Requirements — Individual”
PRD leaves the method “TBD by technical team” but requires confirmation of availability. Add the engineering artifacts the PRD calls for:
- Document collection mechanism (API vs scraping vs user export)
- Document legal/rate-limit constraints
- Confirm which fields in PRD can/can’t be reliably collected now
- Implement a stable “profile snapshot + metrics snapshot” storage model for the required data
---
## 10) Persona switching support
**PRD section:** “3. Six end-goal personas — content framework”
PRD states users can switch personas as goals evolve. Implement:
- Changing persona for a profile
- Persist persona choice and regenerate future calendar/drafts under the new persona
- Keep approval queue consistent (what happens to existing suggested/scheduled items)
---
## Notes / alignment with current repo state
- The repo already has foundations for: LinkedIn profile ingest, audit, deterministic calendar/draft generation, approval workflow, Brand Score v1, and dashboard state.
- The items above are the next PRD-scoped additions that don’t require new platforms or posting.