mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-10-28 15:47:50 +09:00
The self-contained reports slice of the #33 agency-scale milestone, built without the high-blast-radius workspace_id/ACL/branding rework. - WpReportsService aggregates one calendar month per site: uptime % + a daily series + incidents (re-aggregated from #26 HealthCheck samples for the exact month), update runs (#29), DatabaseSnapshot backups, and current vulnerability posture (#28) + health/disk — each labelled persisted-history vs point-in-time. - Persisted (WordPressReport, Alembic 014 off 013_ai_assistant) + synchronous upsert per (site, YYYY-MM), so point-in-time sources (vulns, health) are captured as-of generation while true-historical sources are re-queried per month. Client grouping derives from the client-* tag (#20) — no workspace_id FK. Model lives in wordpress_site.py + lazy-imported in the service so the concurrent AI agent's models/__init__.py is untouched. - Routes GET /reports, POST /reports/generate (admin), DELETE /reports/<id>; client getReports/generateReport/deleteReport; a Reports tab with a recharts daily-uptime chart, summary cards, Print->Save-as-PDF (scoped @media print, no PDF dep) and JSON export. - 9 pytest tests; 3-lens adversarial review (4 of 5 findings fixed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
46 lines
1.5 KiB
Python
46 lines
1.5 KiB
Python
"""Monthly client reports (#33 — agency-scale reports slice).
|
|
|
|
Adds:
|
|
- table wordpress_reports (one persisted report per site per calendar month,
|
|
FK site_id -> wordpress_sites.id)
|
|
|
|
Idempotent: MigrationService._fix_missing_columns runs db.create_all() on boot
|
|
before Alembic, so the table may already exist — guard on the live schema
|
|
exactly like 009/010/011/012/013.
|
|
|
|
Revision ID: 014_wordpress_reports
|
|
Revises: 013_ai_assistant
|
|
Create Date: 2026-06-01
|
|
"""
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
|
|
revision = '014_wordpress_reports'
|
|
down_revision = '013_ai_assistant'
|
|
branch_labels = None
|
|
depends_on = None
|
|
|
|
|
|
def upgrade():
|
|
conn = op.get_bind()
|
|
inspector = sa.inspect(conn)
|
|
|
|
if 'wordpress_reports' not in inspector.get_table_names():
|
|
op.create_table(
|
|
'wordpress_reports',
|
|
sa.Column('id', sa.Integer(), primary_key=True),
|
|
sa.Column('site_id', sa.Integer(), sa.ForeignKey('wordpress_sites.id'), nullable=False, index=True),
|
|
sa.Column('period_label', sa.String(length=7), nullable=False),
|
|
sa.Column('period_start', sa.DateTime(), nullable=False),
|
|
sa.Column('period_end', sa.DateTime(), nullable=False),
|
|
sa.Column('data', sa.Text()),
|
|
sa.Column('generated_at', sa.DateTime()),
|
|
)
|
|
|
|
|
|
def downgrade():
|
|
conn = op.get_bind()
|
|
inspector = sa.inspect(conn)
|
|
if 'wordpress_reports' in inspector.get_table_names():
|
|
op.drop_table('wordpress_reports')
|