v2-exece-docs

This commit is contained in:
IGNY8 VPS (Salman)
2026-03-23 10:30:51 +00:00
parent b94d41b7f6
commit e78a41f11c
15 changed files with 2218 additions and 707 deletions

View File

@@ -1,10 +1,11 @@
# IGNY8 Phase 1: SAG Data Foundation (01A)
## Core Data Models & CRUD APIs
**Document Version:** 1.0
**Document Version:** 1.1
**Date:** 2026-03-23
**Phase:** IGNY8 Phase 1 — SAG Data Foundation
**Status:** Build Ready
**Source of Truth:** Codebase at `/data/app/igny8/`
**Audience:** Claude Code, Backend Developers, Architects
---
@@ -12,8 +13,9 @@
## 1. CURRENT STATE
### Existing IGNY8 Architecture
- **Framework:** Django 5.1 + Django REST Framework 3.15 (upgrading to Django 6.0 on new VPS)
- **Database:** PostgreSQL 16 (upgrading to PostgreSQL 18)
- **Framework:** Django >=5.2.7 + Django REST Framework (from requirements.txt)
- **Database:** PostgreSQL (version set by infra stack)
- **Primary Keys:** BigAutoField (integer PKs — NOT UUIDs). `DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'`
- **Multi-Tenancy:** AccountContextMiddleware enforces tenant isolation
- All new models inherit from `AccountBaseModel` or `SiteSectorBaseModel`
- Automatic tenant filtering in querysets
@@ -26,17 +28,24 @@
}
```
- **ViewSet Pattern:** `AccountModelViewSet` (filters by account) and `SiteSectorModelViewSet`
- **Async Processing:** Celery 5.4 for background tasks
- **Existing Models:**
- Account (tenant root)
- Site (per-account, multi-site support)
- Sector (site-level category)
- Keyword, Cluster, ContentIdea, Task, Content, Image, SiteIntegration
- **Async Processing:** Celery >=5.3.0 for background tasks
- **Existing Models (PLURAL class names, all use BigAutoField PKs):**
- Account, Site (in `auth/models.py`) — `AccountBaseModel`
- Sector (in `auth/models.py`) — `AccountBaseModel`
- Clusters (in `business/planning/models.py`) — `SiteSectorBaseModel`
- Keywords (in `business/planning/models.py`) — `SiteSectorBaseModel`
- ContentIdeas (in `business/planning/models.py`) — `SiteSectorBaseModel`
- Tasks (in `business/content/models.py`) — `SiteSectorBaseModel`
- Content (in `business/content/models.py`) — `SiteSectorBaseModel`
- Images (in `business/content/models.py`) — `SiteSectorBaseModel`
- SiteIntegration (in `business/integration/models.py`) — `SiteSectorBaseModel`
- IntegrationProvider (in `modules/system/models.py`) — standalone
### Frontend Stack
- React 19 + TypeScript 5
- Tailwind CSS 3
- Zustand 4 (state management)
- React ^19.0.0 + TypeScript ~5.7.2
- Tailwind CSS ^4.0.8
- Zustand ^5.0.8 (state management)
- Vite ^6.1.0
### What Doesn't Exist
- Attribute-based cluster formation system
@@ -128,10 +137,11 @@ sag/
class SAGBlueprint(AccountBaseModel):
"""
Core blueprint model: versioned taxonomy & cluster plan for a site.
Inherits: id (UUID), account_id (FK), created_at, updated_at
Inherits: id (BigAutoField, integer PK), account_id (FK), created_at, updated_at
Note: Uses BigAutoField per project convention (DEFAULT_AUTO_FIELD), NOT UUID.
"""
site = models.ForeignKey(
'sites.Site',
'igny8_core_auth.Site', # Actual app_label for Site model
on_delete=models.CASCADE,
related_name='sag_blueprints'
)
@@ -232,7 +242,7 @@ class SAGBlueprint(AccountBaseModel):
class SAGAttribute(AccountBaseModel):
"""
Attribute/dimension within a blueprint.
Inherits: id (UUID), account_id (FK), created_at, updated_at
Inherits: id (BigAutoField, integer PK), account_id (FK), created_at, updated_at
"""
blueprint = models.ForeignKey(
'sag.SAGBlueprint',
@@ -315,7 +325,11 @@ class SAGAttribute(AccountBaseModel):
class SAGCluster(AccountBaseModel):
"""
Cluster: hub page + supporting content organized around attribute intersection.
Inherits: id (UUID), account_id (FK), created_at, updated_at
Inherits: id (BigAutoField, integer PK), account_id (FK), created_at, updated_at
IMPORTANT: This model coexists with the existing `Clusters` model (in business/planning/models.py).
Existing Clusters are pure topic-keyword groups. SAGClusters add attribute-based dimensionality.
They are linked via an optional FK on the existing Clusters model.
"""
blueprint = models.ForeignKey(
'sag.SAGBlueprint',
@@ -323,7 +337,7 @@ class SAGCluster(AccountBaseModel):
related_name='clusters'
)
site = models.ForeignKey(
'sites.Site',
'igny8_core_auth.Site', # Actual app_label for Site model
on_delete=models.CASCADE,
related_name='sag_clusters'
)
@@ -461,8 +475,8 @@ class SectorAttributeTemplate(models.Model):
"""
Reusable template for attributes and keywords by industry + sector.
NOT tied to Account (admin-only, shared across tenants).
Uses BigAutoField PK per project convention (do NOT use UUID).
"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
industry = models.CharField(
max_length=200,
@@ -533,11 +547,12 @@ class SectorAttributeTemplate(models.Model):
All modifications are **backward-compatible** and **nullable**. Existing records are unaffected.
#### **Site** (in `sites/models.py`)
#### **Site** (in `auth/models.py`, app_label: `igny8_core_auth`)
```python
class Site(AccountBaseModel):
class Site(SoftDeletableModel, AccountBaseModel):
# ... existing fields ...
# NEW: SAG integration (nullable, backward-compatible)
sag_blueprint = models.ForeignKey(
'sag.SAGBlueprint',
on_delete=models.SET_NULL,
@@ -548,11 +563,13 @@ class Site(AccountBaseModel):
)
```
#### **Cluster** (in `modules/planner/models.py`)
#### **Clusters** (in `business/planning/models.py`, app_label: `planner`)
```python
class Cluster(AccountBaseModel):
# ... existing fields ...
class Clusters(SoftDeletableModel, SiteSectorBaseModel):
# ... existing fields: name, description, keywords_count, volume,
# mapped_pages, status(new/mapped), disabled ...
# NEW: SAG integration (nullable, backward-compatible)
sag_cluster = models.ForeignKey(
'sag.SAGCluster',
on_delete=models.SET_NULL,
@@ -570,11 +587,16 @@ class Cluster(AccountBaseModel):
)
```
#### **Task** (in `modules/writer/models.py`)
#### **Tasks** (in `business/content/models.py`, app_label: `writer`)
```python
class Task(AccountBaseModel):
# ... existing fields ...
class Tasks(SoftDeletableModel, SiteSectorBaseModel):
# ... existing fields: title, description, content_type, content_structure,
# keywords, word_count, status(queued/completed) ...
# NOTE: Already has `cluster = FK('planner.Clusters')` and
# `idea = FK('planner.ContentIdeas')` — these are NOT being replaced.
# The new sag_cluster FK is an ADDITIONAL link to the SAG layer.
# NEW: SAG integration (nullable, backward-compatible)
sag_cluster = models.ForeignKey(
'sag.SAGCluster',
on_delete=models.SET_NULL,
@@ -592,11 +614,14 @@ class Task(AccountBaseModel):
)
```
#### **Content** (in `modules/writer/models.py`)
#### **Content** (in `business/content/models.py`, app_label: `writer`)
```python
class Content(AccountBaseModel):
class Content(SoftDeletableModel, SiteSectorBaseModel):
# ... existing fields ...
# NOTE: Already has task FK (to writer.Tasks which has cluster FK).
# The new sag_cluster FK is an ADDITIONAL direct link to SAG layer.
# NEW: SAG integration (nullable, backward-compatible)
sag_cluster = models.ForeignKey(
'sag.SAGCluster',
on_delete=models.SET_NULL,
@@ -607,11 +632,12 @@ class Content(AccountBaseModel):
)
```
#### **ContentIdea** (in `modules/planner/models.py`)
#### **ContentIdeas** (in `business/planning/models.py`, app_label: `planner`)
```python
class ContentIdea(AccountBaseModel):
class ContentIdeas(SoftDeletableModel, SiteSectorBaseModel):
# ... existing fields ...
# NEW: SAG integration (nullable, backward-compatible)
sag_cluster = models.ForeignKey(
'sag.SAGCluster',
on_delete=models.SET_NULL,
@@ -847,7 +873,7 @@ class SAGBlueprintViewSet(AccountModelViewSet):
@action(detail=False, methods=['get'])
def active_by_site(self, request):
"""GET /api/v1/sag/blueprints/active_by_site/?site_id=<uuid>"""
"""GET /api/v1/sag/blueprints/active_by_site/?site_id=<int>"""
site_id = request.query_params.get('site_id')
if not site_id:
return Response({
@@ -1027,7 +1053,7 @@ blueprints_router.register(
urlpatterns = [
path('', include(router.urls)),
path('blueprints/<uuid:blueprint_id>/', include(blueprints_router.urls)),
path('blueprints/<int:blueprint_id>/', include(blueprints_router.urls)),
]
```
@@ -1309,7 +1335,7 @@ def compute_blueprint_health(blueprint):
logger.info(f"Computed health for blueprint {blueprint.id}: {blueprint.sag_health_score}")
return {
'blueprint_id': str(blueprint.id),
'blueprint_id': blueprint.id,
'health_score': blueprint.sag_health_score,
'attribute_count': attribute_count,
'cluster_count': cluster_count,
@@ -1580,14 +1606,13 @@ def plan_supporting_content(cluster, hub_page_title, num_articles=5):
2. **Define Models**
- Implement `SAGBlueprint`, `SAGAttribute`, `SAGCluster`, `SectorAttributeTemplate`
- Add 5 new nullable fields to existing models (Site, Cluster, Task, Content, ContentIdea)
- Add 5 new nullable fields to existing models (Site, Clusters, Tasks, Content, ContentIdeas)
- Ensure all models inherit from correct base class (AccountBaseModel or base Model)
3. **Create Migrations**
- Run `makemigrations sag`
- Manually verify for circular imports or dependencies
- Create migration for modifications to existing models
- All existing fields must remain untouched
- Create migration for modifications to existing models (Clusters, Tasks, Content, ContentIdeas in their respective apps; Site in igny8_core_auth)
4. **Implement Serializers**
- SAGBlueprintDetailSerializer (nested attributes & clusters)
@@ -1638,7 +1663,7 @@ def plan_supporting_content(cluster, hub_page_title, num_articles=5):
### Data Model
- [ ] All 4 models created and migrated successfully
- [ ] All 5 existing models have nullable SAG fields
- [ ] All 5 existing models have nullable SAG fields (Site, Clusters, Tasks, Content, ContentIdeas)
- [ ] Unique constraints enforced (blueprint version, attribute slugs, cluster slugs, template industry/sector)
- [ ] Foreign key cascades correct (blueprint → attributes/clusters)
- [ ] All model methods and properties work as documented
@@ -1653,7 +1678,7 @@ def plan_supporting_content(cluster, hub_page_title, num_articles=5):
- [ ] POST /api/v1/sag/blueprints/{id}/archive/ (active → archived)
- [ ] POST /api/v1/sag/blueprints/{id}/regenerate/ (create v+1)
- [ ] POST /api/v1/sag/blueprints/{id}/health_check/ (compute score)
- [ ] GET /api/v1/sag/blueprints/active_by_site/?site_id=<uuid>
- [ ] GET /api/v1/sag/blueprints/active_by_site/?site_id=<int>
- [ ] GET/POST /api/v1/sag/blueprints/{blueprint_id}/attributes/
- [ ] GET/POST /api/v1/sag/blueprints/{blueprint_id}/clusters/
- [ ] GET/POST /api/v1/sag/sector-templates/ (admin-only)
@@ -1711,7 +1736,7 @@ This document contains **everything Claude Code needs to build the sag/ app**.
6. **Copy admin.py exactly** as-is
7. **Create service files** with code from Section 3.7
8. **Create AI function stubs** from Section 3.8
9. **Create migration** for existing model changes (Site, Cluster, Task, Content, ContentIdea)
9. **Create migration** for existing model changes (Site in `igny8_core_auth`, Clusters/ContentIdeas in `planner`, Tasks/Content in `writer`)
10. **Run migrations** on development database
11. **Test endpoints** with Postman or curl
12. **Write unit & integration tests** matching patterns in existing test suite
@@ -1724,7 +1749,7 @@ python manage.py startapp sag igny8_core/
# Makemigrations
python manage.py makemigrations sag
python manage.py makemigrations # For existing model changes
python manage.py makemigrations igny8_core_auth planner writer # For existing model changes
# Migrate
python manage.py migrate sag
@@ -1753,7 +1778,7 @@ curl -X POST http://localhost:8000/api/v1/sag/blueprints/ \
-H "Authorization: Token <YOUR_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"site": "<site-uuid>",
"site": 42,
"status": "draft",
"source": "manual",
"taxonomy_plan": {}
@@ -1800,7 +1825,7 @@ POST /api/v1/sag/blueprints/
Authorization: Token <token>
{
"site": "550e8400-e29b-41d4-a716-446655440000",
"site": 42,
"status": "draft",
"source": "manual",
"taxonomy_plan": {
@@ -1819,9 +1844,9 @@ Authorization: Token <token>
{
"success": true,
"data": {
"id": "660e8400-e29b-41d4-a716-446655440001",
"site": "550e8400-e29b-41d4-a716-446655440000",
"account": "770e8400-e29b-41d4-a716-446655440002",
"id": 1,
"site": 42,
"account": 7,
"version": 1,
"status": "draft",
"source": "manual",
@@ -1856,7 +1881,7 @@ Authorization: Token <token>
**Request:**
```json
POST /api/v1/sag/blueprints/660e8400-e29b-41d4-a716-446655440001/confirm/
POST /api/v1/sag/blueprints/1/confirm/
Authorization: Token <token>
```
@@ -1865,8 +1890,8 @@ Authorization: Token <token>
{
"success": true,
"data": {
"id": "660e8400-e29b-41d4-a716-446655440001",
"site": "550e8400-e29b-41d4-a716-446655440000",
"id": 1,
"site": 42,
"version": 1,
"status": "active",
"confirmed_at": "2026-03-23T10:05:00Z",