- 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
28 KiB
Social Branding Service Audit
Date: 2026-03-23
Purpose
This document compares:
- the product BRD / PRD in
docs/PRD.md - the narrowed execution plan in
docs/plan.md - the current implementation in
app/andtests/
It is intended as a handoff and presentation document so another engineer or stakeholder can answer:
- what was planned
- what was actually built
- which decisions were taken to narrow scope
- which assumptions phase one was built on
- what is still missing versus the plan
- what is still missing versus the PRD
- what architecture should be used going forward
Auth is intentionally excluded from blocker status for this review. Future auth is assumed to be Clerk-issued JWT at the service boundary.
Executive Summary
The current repo is best understood as a narrowed, LinkedIn-first individual-user MVP that is substantially aligned with the overnight plan, but not aligned with the full PRD.
The implementation does support the plan's core demo loop:
- import a LinkedIn profile snapshot
- compute a LinkedIn audit and Brand Score v1
- infer or select a persona strategy
- generate a 2-week content calendar with an 80/20 split
- generate LinkedIn drafts
- move drafts through approval, rejection, regeneration, and manual publish confirmation
- expose dashboard and local event history
That flow is implemented and tested. uv run pytest passed with 4 passed, 1 warning on 2026-03-23.
However, the repo is not PRD-complete. The PRD describes a broader multi-platform, more automated system with richer scoring, profile rewrites, live collection, scheduled posting, analytics, engagement automation, and enterprise workflows. Those are not implemented here.
The biggest architectural mismatch with the desired next version is this:
- the code already has some engine-like pure logic
- but the engine is not yet separated as an explicit domain package or service boundary
- orchestration, persistence, and business logic are still mixed inside one application service
- surfaces are currently HTTP-only in practice
- Redis and the worker exist as scaffolding, not as an active runtime path
Canonical Scope Hierarchy
The repo currently has three levels of scope, and they should not be confused during presentation.
1. PRD scope
The PRD is the broad product target:
- individual plus enterprise
- LinkedIn, Instagram, X, YouTube
- automated connect -> collect -> analyze -> generate -> approve -> post -> track loop
- Brand Score feeding RQx
- richer content and analytics behavior
Relevant references:
docs/PRD.md:21-36docs/PRD.md:42-70docs/PRD.md:80-102docs/PRD.md:183-248docs/PRD.md:360-397
2. Plan scope
The plan is the locked overnight MVP and is the real governing doc for what this repo was trying to ship immediately:
- individual only
- LinkedIn only
- manual import fallback instead of hard OAuth
- Brand Score v1 from available LinkedIn data
- content calendar, drafts, approval queue, manual publish confirmation
- dashboard and local events
- enterprise and multi-platform behavior deferred
Relevant references:
docs/plan.md:9-38docs/plan.md:78-128docs/plan.md:149-158
3. Current implementation scope
The code matches the narrowed plan much more closely than it matches the PRD. It is therefore more accurate to describe the current service as:
"A request-driven, LinkedIn-first social branding MVP for individual users with manual ingestion, simplified scoring, deterministic content generation, approval workflow, dashboard summaries, and local event persistence."
That description is also how the README frames it:
README.md:3-23README.md:81-85
Current Architecture Assessment
What exists today
The current architecture is:
- HTTP API surface in
app/api/v1/ - one application service in
app/services/social_branding.py - several pure or mostly pure helper modules in:
app/services/brand_score.pyapp/services/persona_strategy.pyapp/services/content_calendar.py
- persistence in SQLAlchemy models in
app/models.py - queue abstraction in
app/services/queue.py - a worker loop in
app/worker/main.py
This is a valid MVP architecture, but it is not yet the target split you described.
Desired target architecture
For the next version, the clean split should be:
1. Social branding engine
A pure, platform-agnostic decision engine that takes normalized inputs and produces outputs such as:
- audits
- scores
- persona strategy decisions
- calendar plans
- draft suggestions
- recommendation bundles
This layer should not know about:
- FastAPI
- SQLAlchemy sessions
- Clerk JWTs
- Redis
- Postgres
- HTTP request shapes
2. Social branding service layer
An orchestration layer that:
- receives authenticated user context
- loads and stores data
- calls the engine
- persists outputs
- emits events
- coordinates approval and posting workflows
- exposes use cases to surfaces
3. Surfaces
Adapters on top of the service layer, for example:
- HTTP API
- background worker consumers
- Redis/event consumers
- internal command handlers
- future admin or operator surfaces
4. External adapters
Per-platform collectors and posters:
- LinkedIn import adapter
- Instagram connector
- X connector
- YouTube connector
- GitHub connector (signals + content source)
- manual upload/import adapter
See also: docs/external-adapters-plan.md for the platform-compliance contract and phased implementation plan.
5. Infrastructure
- Postgres
- Redis
- storage
- auth verification
- observability
How close the current code is to that shape
Partially aligned:
app/services/brand_score.pyalready looks like engine logic.app/services/persona_strategy.pyalready looks like engine logic.app/services/content_calendar.pyalready looks like engine logic.
Not yet aligned:
app/services/social_branding.pycombines orchestration, persistence, workflow rules, and business decisions in one class.- there is no explicit normalized engine contract
- there is no repository layer
- there are no platform adapter interfaces
- surfaces are API routes only in practical use
Conclusion:
The repo contains the beginnings of an engine, but the engine is still embedded inside the service implementation rather than clearly separated.
What Was Implemented
Data model and persistence
The main persistence model is present and broadly follows the plan:
social_accountssocial_profile_snapshotsbrand_scorespersona_strategiescontent_calendar_itemscontent_draftsapproval_queueposting_recordsanalytics_snapshotsengagement_draftsintegration_events
Reference:
app/models.py:58-235
Observations:
- The model names mostly match the plan's domain model.
posting_recordsandintegration_eventswere added beyond the explicit plan list and are useful.engagement_draftsexists in schema only and is not actively used in the service flow.- Everything is stored through
create_all()on startup; there is no migration system yet.
API surface
The HTTP routes listed in the README and plan are implemented:
- LinkedIn import
- persona select
- brand score recalculate
- content calendar generate/list
- drafts generate
- approval approve/reject/regenerate
- publish confirm
- dashboard
- events
- demo seed
- health
References:
README.md:25-39app/api/v1/__init__.pyapp/api/v1/accounts.pyapp/api/v1/brand_score.pyapp/api/v1/content.pyapp/api/v1/dashboard.pyapp/api/v1/events.pyapp/api/v1/demo.pyapp/api/v1/health.pyapp/api/v1/persona.py
LinkedIn ingest and audit
Implemented:
- manual LinkedIn import payload
- account upsert
- profile snapshot persistence
- section-level audit scoring
- recommendation list persistence
References:
app/services/social_branding.py:190-239app/services/brand_score.py:37-88
What it actually does:
- scores headline, about, experience, skills, recommendations, featured, and activity
- persists overall score and recommendations
- uses simple heuristic thresholds, not benchmarked analysis
Brand Score v1
Implemented:
- Brand Score computation on import
- manual recalculation endpoint
- score history persistence
- event emission when score changes materially or when forced
References:
app/services/social_branding.py:134-188app/services/social_branding.py:241-274app/services/brand_score.py:91-142
What it actually does:
- computes five components:
- profile completeness
- content consistency
- engagement quality
- visibility
- audience growth
- uses LinkedIn metrics only
- stores local integration events in the service database
Persona strategy
Implemented:
- six persona enums
- heuristic persona inference from pathway context
- manual persona override
- cadence, tone, target role, and source context persistence
References:
app/models.py:35-41app/services/persona_strategy.py:8-127app/services/social_branding.py:67-96
What it actually does:
- infers persona from role and goals
- always assigns LinkedIn as the active platform
- stores one active strategy at a time per user
Content calendar and drafts
Implemented:
- 2-week calendar generation
- 80/20 toolkit versus dynamic split
- deterministic topic sequencing
- LinkedIn draft generation
References:
app/services/social_branding.py:276-322app/services/content_calendar.py:26-84app/services/social_branding.py:360-459app/services/content_calendar.py:97-144
What it actually does:
- uses persona cadence to create slots over 14 days
- marks some slots as dynamic using deterministic spacing
- chooses from a fixed toolkit topic list per persona
- generates simple template-based LinkedIn drafts
Approval and publish flow
Implemented:
- queue item creation per draft
- approve
- reject
- regenerate
- posting state transitions
- manual publish confirmation
- local approval event emission
References:
app/services/social_branding.py:333-359app/services/social_branding.py:360-418app/services/social_branding.py:486-620
What it actually does:
- every draft becomes an approval item and posting record
- approving immediately transitions the posting record to
ready_for_manual_publish - publish is confirmed manually by user-supplied external reference
Dashboard and events
Implemented:
- latest brand score and recent trend
- queue summary
- posting state summary
- latest analytics snapshot view
- local event listing
References:
app/services/social_branding.py:622-705app/services/social_branding.py:707-722
Worker and queue
Implemented in scaffold form:
- queue backend abstraction
- database no-op backend
- Redis backend
- worker loop
References:
app/services/queue.py:1-83app/worker/main.py:14-36README.md:83-85
Important reality:
- the current product flow is request-driven
- the worker does not execute domain jobs
- the Redis queue is infrastructure-ready, not feature-ready
Test coverage
Implemented:
- health check
- happy path full MVP flow
- limited-data import path
- regenerate path state behavior
Reference:
tests/test_social_branding_flow.py:32-207
Observed verification:
- command run:
uv run pytest - result:
4 passed, 1 warning
Phase-One Foundations: Decisions and Assumptions
The user asked specifically for the assumptions and decisions behind the current phase-one implementation. Those are below.
Decision 1: Narrow to individual plus LinkedIn only
Why:
- the PRD is much broader than can be implemented quickly
- LinkedIn is the primary value surface in the PRD and plan
- multi-platform plus enterprise would have blocked delivery
Where it shows up:
docs/plan.md:11-30README.md:10-23app/services/persona_strategy.py:124app/services/social_branding.py:201-206app/services/social_branding.py:313
Assumption:
- proving one strong individual LinkedIn flow is more valuable than partial support across four platforms
Decision 2: Use manual import instead of OAuth-first collection
Why:
- the PRD explicitly leaves data collection method to engineering
- LinkedIn collection is the riskiest integration area
- the overnight plan avoids blocking on OAuth and unstable APIs
Where it shows up:
docs/PRD.md:36docs/PRD.md:240-248docs/plan.md:13docs/plan.md:35app/schemas/social_branding.py:47-52app/services/social_branding.py:190-239
Assumption:
- GrowQR can seed or receive normalized LinkedIn data from a manual process, future adapter, or another service
Decision 3: Treat Brand Score as a simplified v1
Why:
- the PRD's full seven-component score depends on data that does not exist in the narrowed MVP
- the plan explicitly says not to block on unavailable cross-platform metrics
Where it shows up:
docs/plan.md:36app/services/brand_score.py:18-24app/services/brand_score.py:91-142
Assumption:
- a meaningful but incomplete score is acceptable for v1 if it is stable, stored, and trendable
Decision 4: Use deterministic content generation instead of real AI orchestration
Why:
- the plan prioritizes demonstrating flow and content structure over model sophistication
- deterministic output is easy to test and explain
Where it shows up:
docs/plan.md:38app/services/content_calendar.py:26-84app/services/content_calendar.py:97-144
Assumption:
- content templates and placeholders are enough for MVP validation
Decision 5: Make publish manual-confirmed
Why:
- the plan explicitly allows manual publish confirmation
- live posting would introduce platform API risk and auth complexity
Where it shows up:
docs/plan.md:37app/services/social_branding.py:507-508app/services/social_branding.py:591-620
Assumption:
- for v1, it is enough to generate approved content and let the user confirm it was posted manually
Decision 6: Keep async infrastructure scaffolded but not operationally central
Why:
- Postgres and Redis are useful for future scale and service consistency
- the immediate flow can be run synchronously
Where it shows up:
README.md:75-85.env.example.env.dockerapp/services/queue.py:1-83app/worker/main.py:14-36
Assumption:
- request-driven synchronous use cases are acceptable for phase one, with queue infrastructure reserved for later work
Decision 7: Trust caller-provided user_id
Why:
- auth is intentionally out of scope
Where it shows up:
- almost every request schema accepts
user_iddirectly, for exampleapp/schemas/social_branding.py:47-52,87-91,111-115,153-157
Assumption:
- an upstream system or future JWT layer will replace body-supplied identity
Implementation Status Versus Plan
The plan is mostly implemented, but some items are implemented in a thin MVP form rather than a durable production form.
Phase 1: Foundations
Plan status: implemented
Evidence:
- social branding models exist in
app/models.py:58-235 - persona enums exist in
app/models.py:35-41 - brand score component model exists in
app/models.py:89-99 - persona topic libraries exist in
app/services/persona_strategy.py:26-63
Assessment:
- This phase is done for MVP purposes.
- The main gap is architectural: these foundations are not yet separated into engine/service/adapters.
Phase 2: Profile ingest and audit
Plan status: implemented, simplified
Evidence:
- import endpoint exists
- manual payload fallback is the main supported path
- audit scoring and recommendations are persisted
- limited-data import test exists
Assessment:
- Done for MVP.
- Not equivalent to PRD-grade platform analysis.
Phase 3: Brand Score and persona strategy
Plan status: implemented, simplified
Evidence:
- brand score is computed and stored
- persona inference and manual selection work
- score update events are persisted
Assessment:
- Done for MVP.
- The score model is intentionally reduced from the PRD.
- Events are local persistence records, not cross-service integration delivery.
Phase 4: Content calendar and drafts
Plan status: implemented, partially shallow
Evidence:
- 2-week calendar exists
- 80/20 split exists
- LinkedIn draft generation exists
Assessment:
- Done for demo and workflow validation.
- Dynamic triggers are represented as preselected topic types, not event-driven runtime behavior.
- Drafts are deterministic templates, not model-generated or personalized beyond a few fields.
Phase 5: Approval and publish flow
Plan status: implemented
Evidence:
- queue, approve, reject, regenerate, publish confirmation, and state transitions all exist
Assessment:
- Strongly implemented for the narrowed MVP.
- Still single-user only.
- No scheduling engine or platform posting adapter exists.
Phase 6: Dashboard and hardening
Plan status: mostly implemented
Evidence:
- dashboard endpoint exists
- happy-path tests exist
- deferred scope is documented
Assessment:
- Done for MVP handoff and demo.
- Not hardening-complete for production.
- Missing migrations, background jobs, external integrations, auth, observability, and deeper test coverage.
Implementation Status Versus PRD
This is the most important presentation distinction:
- plan alignment: mostly yes
- PRD alignment: no, not yet
PRD items that are fully or mostly covered
Individual personas exist
- six personas exist in the model
- persona selection and switching are present
Caveat:
- active platform mix is not implemented beyond LinkedIn
LinkedIn-first audit and content flow exists
- LinkedIn import
- audit
- Brand Score v1
- calendar
- approval queue
- manual publish confirmation
Brand Score history and dashboard exposure exist
- score history is persisted
- dashboard returns recent trend
PRD items that are only partially covered
Brand Score formula
PRD expectation:
- seven components
- RQx formula
- downstream event to Assessment and Pathways services
Current implementation:
- five components only
- no RQx mapping
- local event persistence only
References:
docs/PRD.md:82-102app/services/brand_score.py:18-24app/services/social_branding.py:174-188
Content model
PRD expectation:
- rich toolkit catalog and multiple dynamic triggers
- platform-aware formatting and platform mix rules
Current implementation:
- reduced toolkit topics
- three dynamic topic types
- LinkedIn-only output
References:
docs/PRD.md:141-181app/services/persona_strategy.py:26-63app/services/content_calendar.py:38-43
Analytics
PRD expectation:
- platform analytics per sync cycle
- richer performance tracking and trend lines
Current implementation:
- only snapshot summaries based on imported metrics
- no real recurring sync
- no per-content performance table
References:
docs/PRD.md:243-248docs/PRD.md:371-381app/models.py:198-211app/services/social_branding.py:656-696
PRD items that are not implemented
Multi-platform runtime support
Not implemented:
- Instagram runtime collection/generation/posting
- X runtime collection/generation/posting
- YouTube analysis/brief generation/posting pipeline
Even though platform enums exist, runtime behavior does not.
References:
docs/PRD.md:21-24docs/PRD.md:198-235app/models.py:28-32
OAuth connect and recurring sync
Not implemented:
- platform OAuth flow
- scheduled sync jobs
- recurring collection cadence
- delta-based collector jobs
References:
docs/PRD.md:63-70docs/PRD.md:243-248app/worker/main.py:14-36
Profile rewrites and application to platform
Not implemented:
- rewrite generation for headline/about/experience
- approval flow for profile updates
- apply-to-platform action
References:
docs/PRD.md:190-193- no corresponding route or service use case exists
Recruiter visibility and keyword gap analysis
Not implemented beyond basic visibility scoring inputs.
Missing:
- target-role benchmark
- keyword gap detection
- profile rewrite recommendations based on gaps
Auto-posting and scheduling
Not implemented.
Current system only records manual publish confirmation.
References:
docs/PRD.md:68-70docs/PRD.md:179docs/PRD.md:369-381app/services/social_branding.py:591-620
Auto-approve mode
Not implemented.
The PRD explicitly calls for opt-in auto-approve.
References:
docs/PRD.md:179docs/PRD.md:376
Sensitive-topic filter and tone lock
Not implemented.
Missing:
- sensitive-topic classification before queueing
- tone locking after initial brand establishment
- policy-based regeneration or suppression
References:
docs/PRD.md:176-181
Engagement automation
Not implemented.
Notes:
- the table
engagement_draftsexists - no service flow populates it
- no routes expose it
References:
docs/PRD.md:56docs/PRD.md:168docs/PRD.md:194-195app/models.py:214-225
Enterprise social branding
Not implemented.
That entire scope is still absent.
References:
docs/PRD.md:250-397docs/plan.md:25
Key Product and Technical Assumptions Embedded in the Current Repo
These assumptions should be stated explicitly in any presentation so the current implementation is not oversold.
Assumption: one service owns both business workflow and persistence
The code assumes one deployable service can hold:
- workflow logic
- persistence rules
- local event persistence
- API-facing use cases
This is acceptable for MVP, but it is why the engine is not yet truly reusable.
Assumption: normalized external data will eventually arrive
The import request shape assumes someone else can provide:
- profile sections
- aggregate metrics
- pathway context
without this service solving collection first.
Assumption: demo readiness was more important than platform completeness
This is visible in:
- deterministic content generation
- manual publish confirmation
- local event storage
- request-driven flow
Assumption: Postgres and Redis are deployment options, not proof of async completeness
The repo can run on Postgres and Redis in Docker, but that should not be presented as meaning:
- scheduled jobs are implemented
- queue processing is meaningful
- Redis is part of the live product loop today
Assumption: auth will be added upstream later
Today the service trusts request bodies for user_id.
For Clerk JWT integration, the service should move to:
- derive subject from JWT
- remove
user_idfrom externally writable bodies where possible - enforce per-user access at the service layer
What Is Missing to Finish the Plan Properly
Strictly speaking, the plan's demo definition is met. But if "finish the plan properly" means "make the narrowed MVP robust and presentation-safe for handoff", the remaining work is:
1. Separate the engine from the service layer
Required:
- create an explicit engine package
- move pure decision logic out of application orchestration
- define normalized engine input and output contracts
Why:
- this is the biggest gap against the intended architecture
- it makes later multi-surface work cleaner
2. Add repository and adapter boundaries
Required:
- persistence repositories
- platform import adapters
- event publisher abstraction
- posting adapter abstraction
Why:
- right now platform, persistence, and workflow assumptions are tightly coupled
3. Replace startup create_all() with migrations
Required:
- Alembic or equivalent
Why:
- handoff and deployment reliability
- production schema evolution
4. Turn queue and worker into real use-case execution paths
Required:
- actual queued job types
- background handlers
- idempotency rules
- retry behavior
Why:
- the current worker is only scaffolded
5. Add stronger tests
Required:
- service-layer unit tests for engine outputs
- route-level error case tests
- Postgres integration tests
- Redis queue tests if worker becomes real
Why:
- current tests prove happy-path MVP behavior only
What Is Missing to Reach the PRD
This is the longer roadmap beyond the narrowed MVP.
Individual PRD completion
Required work:
- live or semi-live collection strategy per platform
- recurring sync engine
- full seven-component Brand Score
- RQx mapping and real cross-service event delivery
- profile rewrite generation and apply flow
- richer toolkit library
- real dynamic trigger ingestion
- topic safety filtering
- tone lock
- optional auto-approve mode
- scheduled posting adapters
- platform-specific formatting
- engagement queue generation
- richer analytics model and trend views
- platform activation recommendations per persona/archetype
Enterprise PRD completion
Required work:
- enterprise account domain
- company brand voice settings
- governance rules and flags
- multi-role approvals
- enterprise content types
- enterprise analytics
- enterprise content calendar visibility and role actions
Recommended Next Architecture and Work Packages
If the goal is to build toward the plan and finally the PRD while matching the desired split, the implementation sequence should be:
Work Package 1: Explicit engine extraction
Create a package such as app/engine/ or app/domain/engine/ with:
- audit engine
- score engine
- persona engine
- calendar engine
- draft engine
Inputs should be normalized Python models, not SQLAlchemy rows.
Work Package 2: Application service boundary
Refactor SocialBrandingService into use cases such as:
- import_profile
- select_persona
- recalculate_brand_score
- generate_calendar
- generate_drafts
- approve_content
- reject_content
- regenerate_draft
- confirm_publish
- get_dashboard
Each use case should orchestrate repositories and engine calls.
Work Package 3: Adapter layer
Add adapter interfaces for:
- profile collectors
- posting executors
- event publishers
- analytics ingestors
Start with:
- manual import adapter
- LinkedIn placeholder adapter
Work Package 4: Surface layer cleanup
Keep surfaces thin:
- HTTP API maps requests to use cases
- worker consumes jobs and invokes use cases
- Redis/event subscribers invoke the same use cases
Work Package 5: Auth integration
When Clerk JWT is added:
- resolve user identity from token
- remove body-driven identity where possible
- add service-layer authorization guards
Work Package 6: Platform expansion
After the architecture split is in place:
- add Instagram adapter
- add X adapter
- add YouTube adapter
Do not add those directly into the current SocialBrandingService shape, or the service will become harder to evolve.
Final Readout
What is done
The narrowed LinkedIn-first MVP is real and working:
- ingest
- audit
- Brand Score v1
- persona strategy
- 2-week calendar
- LinkedIn drafts
- approval queue
- manual publish confirmation
- dashboard
- local events
- demo seed
- tests passing
What decisions were taken
The implementation intentionally chose:
- LinkedIn-only runtime
- manual import over OAuth-first
- simplified Brand Score
- deterministic templates
- manual publish confirmation
- request-driven flow
- local event persistence
- deferred auth
What assumptions phase one was built on
Phase one assumes:
- upstream or manual data can be provided
- one-user flows are enough for validation
- live posting is not required
- local persistence is enough to prove the workflow
- Redis/Postgres readiness is useful even without full async execution
- the engine can be extracted later from the current helper modules and service logic
What is left versus the plan
Very little is left for the narrowed MVP feature set itself. The main unfinished work is structural and production-grade:
- engine extraction
- repository/adaptor boundaries
- migrations
- real worker jobs
- broader tests
What is left versus the PRD
A large amount remains:
- multi-platform support
- real collection and sync
- richer scoring and RQx integration
- profile rewrites
- scheduled posting
- engagement automation
- richer analytics
- safety and policy logic
- enterprise workflows
Bottom line
This repo should be presented as:
"Plan-complete for a narrowed LinkedIn-first MVP, but not PRD-complete. The next major step is not adding random features inside the current service. It is separating the engine from the service and surfaces so the PRD can be implemented cleanly."