Loganix Ops

Content — QA Review Form

Schema Mapping

Content fulfillment lives across four tables: writers (the third-party writers), content_docs (one Google Doc per order), content_assignments (which writer is working on the doc — reassignable), and content_activity_log. The order-level status moves forward when content_docs.handed_off_at is populated.

Writers
nameWriter name
contact_emailContact email
paypal_emailPayout email
nichePrimary niche
interestsTopic prefs (+)
negative_interestsTopic prefs (−)
cost_per_wordRate
max_articles_per_weekCapacity
timezoneTimezone
working_hoursWorking hours
bannedBanned
claimedProfile claimed
Content Doc
order_idParent order
doc_urlGoogle Doc URL
word_countWord count
word_count_estimateEstimate
categoryCategory
statusDoc status
admin_notesAdmin notes
late_atLate threshold
archivedArchived
QA & Handoff
qa_approvedQA approved
qa_approved_byQA approved by
qa_approved_atQA approved at
qa_notesQA notes
last_ai_checkAI detection
last_copyscapeCopyscape
handed_off_atHanded to fulfillment
Assignment
writer_idWriter
statusWriter workflow
ai_guidelinesAI guidelines
ai_ideasAI ideas
reviewer_user_idTeam-lead reviewer
qa_reviewed_atLast QA review
revision_countRevision loops
is_currentActive assignment
History
content_activity_log

QA Review Form — Not Connected to Database

The content layer has four tables — writers, content_docs, content_assignments, content_activity_log. This page shows every field on each so the content team (writers, team leads) can QA the schema against the portal they'll use.

Content

A parent order enters the content stage → a content_docs row is created → assigned to a writer → writer drafts the article → team lead QAs → doc is handed off back to fulfillment. The handoff today is manual; handed_off_at is the hook for automating it.

Writer Workflow States — content_assignments.status

queued assigned writing submitted_for_qa revision_requested qa_approved handed_off | canceled
revision_requested loops back to writing and increments revision_count. canceled is terminal from any state. Values are documented via COMMENT ON but not CHECK-enforced — we can add new substates later without a migration.
1

Writer Profile

writers

Third-party writers; not staff. Kept distinct from users (staff) and suppliers (vendors / publishers) because they have a narrower feature set and different pay model (per-word vs. per-placement).

Display name; unique.
writers.name — varchar(255), UNIQUE, NOT NULL
Where we reach them for assignments and feedback.
writers.contact_email — varchar(255)
Where we send payments (may differ from contact email).
writers.paypal_email — varchar(255)
Their dominant subject area; used to match to orders.
writers.niche — varchar(255)
Topics they're comfortable with.
writers.interests — text
Topics they won't write about.
writers.negative_interests — text
Their rate; multiplied by word count for payout.
writers.cost_per_word — numeric(10,4)
$ / word
Weekly capacity; queue picker respects this limit.
writers.max_articles_per_week — integer
IANA timezone string.
writers.timezone — varchar(50)
Freeform schedule notes.
writers.working_hours — text
Writer is blocked from receiving new assignments.
writers.banned — boolean, default false
Banned from assignments
Writer has logged into the portal and claimed this profile (vs. a staff-created shell).
writers.claimed — boolean, default false
Profile claimed
2

Content Doc

content_docs

One Google Doc per order. The same doc may be reassigned across writers (via content_assignments); this row stays the same.

FK to orders. One doc per order.
content_docs.order_id — integer FK
Where the writer drafts the content.
content_docs.doc_url — varchar(255)
Doc-level summary. Mirrors the active assignment's writer workflow for fast order-side queries.
content_docs.status — varchar(30), NOT NULL, default 'new'
Final word count; recorded on submission.
content_docs.word_count — integer
Target range the writer commits to.
content_docs.word_count_estimate — varchar(255)
Content category (niche alignment with writer interests).
content_docs.category — varchar(255)
When this doc is considered overdue. Drives alerts.
content_docs.late_at — timestamptz
Staff-only notes on the doc. Not visible to the writer.
content_docs.admin_notes — text
Doc is archived (order canceled, stale, etc.).
content_docs.archived — boolean
Archived
3

Current Assignment

content_assignments

The active writer on this doc. Prior assignments stay in the table with is_current = false. Team lead (reviewer_user_id) is from the users table — internal staff, not a third-party writer.

FK to writers. Reassignable via new rows with is_current flipped.
content_assignments.writer_id — integer FK
The writer-side workflow state. See the state-flow diagram above.
content_assignments.status — varchar(20)
Guidelines passed to the writer's AI assistant (style, tone, banned phrases).
content_assignments.ai_guidelines — text
Pre-generated angles or outlines the writer can riff on.
content_assignments.ai_ideas — text
True for the current writer. Reassignments flip old rows to false and insert a new true row.
content_assignments.is_current — boolean
Current assignment
Which internal staff user (team lead / editor) last reviewed this assignment. Distinct from writer_id. ON DELETE SET NULL so review history survives if the reviewer leaves.
content_assignments.reviewer_user_id — integer FK → users
Looked up / created in users by email.
When the team lead last completed a QA review (approve or revision_requested). Null while still in writing.
content_assignments.qa_reviewed_at — timestamptz
How many times the team lead sent this assignment back for revision. Increments each submitted_for_qa → revision_requested transition. Signal for writer quality trends.
content_assignments.revision_count — smallint, default 0
Notes from the team lead specific to this assignment attempt. Doc-level content_docs.qa_notes is for the final QA snapshot.
content_assignments.qa_notes — text
4

QA & Handoff

content_docs

Doc-level QA snapshot. When handed_off_at is populated, fulfillment owns the content; the order's content_url is synced and the supplier can be notified.

Final QA pass on the doc. Separate from per-assignment review.
content_docs.qa_approved — boolean
QA passed
Who gave the final sign-off. Plain string today; may migrate to a users FK later.
content_docs.qa_approved_by — varchar(255)
When the final QA pass was recorded.
content_docs.qa_approved_at — timestamptz
Final QA notes on the doc.
content_docs.qa_notes — text
Last AI-detection scan result (pass/fail + score).
content_docs.last_ai_check — text
Last Copyscape plagiarism scan result.
content_docs.last_copyscape — text
When the doc was handed off to fulfillment — the signal that unblocks the supplier notification step. Today: set manually by staff when QA passes. Future: auto-set when qa_approved=true and fulfillment picks it up. Null = not yet handed off.
content_docs.handed_off_at — timestamptz
Replaces the legacy sent boolean.
5

Activity Log (display-only)

content_activity_log

Append-only trail of content actions — writer picks up, submits, team lead reviews, revision requested, handed off, archived. Populated by the portal and cron; staff rarely write directly.