trading-platform/orchestration/tareas/TASK-2026-02-05-ANALISIS-VALIDACION-MODELADO-BD/entregables/FASE3-INTEGRIDAD-AUDITORIA.yml
Adrian Flores Cortes b9098ca91c [TASK-2026-02-05-ANALISIS-VALIDACION-MODELADO-BD] docs: Complete 6-phase database modeling analysis
Comprehensive analysis of 101 DDL tables across 11 schemas:
- Phase 1-2: Schema validation, 37 gaps cataloged (3 resolved)
- Phase 3: Integrity audit (80 FKs, 89 CHECKs, 17 issues: 2 CRIT/5 HIGH)
- Phase 4: DDL-Backend mapping (84% interfaces, 75% services, 61% controllers)
- Phase 5: Documentation purge catalog (201 files analyzed)
- Phase 6: Remediation plan (4 sprints, 204h)

Key finding: Backend uses raw SQL + pg Pool (NOT TypeORM).
13 deliverables + updated inventories to v2.0.0.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-05 16:48:45 -06:00

2603 lines
83 KiB
YAML

# ============================================================================
# FASE 3 - INTEGRIDAD Y AUDITORIA COMPLETA
# Trading Platform DDL - Database Integrity Specialist Report
# ============================================================================
# Generated: 2026-02-05
# Agent: Database Integrity Specialist (Claude Opus 4.6)
# Scope: ALL 11 schemas, 101 tables, all FK/constraints/functions/triggers
# DDL Source: projects/trading-platform/apps/database/ddl/
# ============================================================================
metadata:
task_id: TASK-2026-02-05-ANALISIS-VALIDACION-MODELADO-BD
phase: 3
phase_name: "Integridad y Auditoria"
generated_at: "2026-02-05T00:00:00Z"
agent: "Database Integrity Specialist"
model: "claude-opus-4-6"
# ============================================================================
# STATISTICS SUMMARY
# ============================================================================
statistics:
schemas_total: 11
schemas_in_01_schemas_sql: 10
schemas_self_created: 1 # feature_flags creates its own schema in 01-flags.sql
schemas_list:
- auth
- trading
- education
- financial
- investment
- portfolio
- ml
- llm
- audit
- market_data
- feature_flags
tables_per_schema:
auth: 12 # users, user_profiles, oauth_accounts, sessions, email_verifications, phone_verifications, password_reset_tokens, auth_logs, login_attempts, rate_limiting_config, notifications, user_push_tokens
trading: 13 # symbols, watchlists, watchlist_items, bots, orders, positions, trades, signals, trading_metrics, paper_balances, price_alerts, drawing_tools, drawing_templates
education: 19 # categories, courses, modules, lessons, enrollments, progress, quizzes, quiz_questions, quiz_attempts, certificates, user_achievements, user_gamification_profile, user_activity_log, course_reviews, videos, review_helpful_votes, instructors, course_tags, course_tag_assignments
financial: 11 # wallets, wallet_transactions, subscriptions, invoices, payments, wallet_audit_log, currency_exchange_rates, wallet_limits, customers, payment_methods, refunds
investment: 10 # products, risk_questionnaire, accounts, distributions, transactions, withdrawal_requests, daily_performance, distribution_history, distribution_runs, agent_executions
portfolio: 5 # portfolios, portfolio_allocations, portfolio_goals, rebalance_history, portfolio_snapshots
ml: 12 # models, model_versions, predictions, prediction_outcomes, feature_store, llm_predictions, llm_prediction_outcomes, llm_decisions, risk_events, backtest_runs, llm_signals, prediction_overlays
llm: 5 # conversations, messages, user_preferences, user_memory, embeddings
audit: 7 # audit_logs, security_events, system_events, trading_audit, api_request_logs, data_access_logs, compliance_logs
market_data: 4 # tickers, ohlcv_5m, ohlcv_15m, ohlcv_5m_staging
feature_flags: 3 # flags, user_flags, evaluations
tables_total: 101
foreign_keys_total: 80
check_constraints_total: 89
unique_constraints_total: 32
functions_total: 36
triggers_total: 46
views_total: 14
enums_total: 42
# ============================================================================
# A. FK VALIDATION MATRIX
# ============================================================================
fk_validation_matrix:
# ---- AUTH SCHEMA ----
- source: auth.user_profiles.user_id
target: auth.users.id
cross_schema: false
on_delete: CASCADE
valid: true
notes: "1:1 relationship, UNIQUE on user_id"
- source: auth.oauth_accounts.user_id
target: auth.users.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: auth.sessions.user_id
target: auth.users.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: auth.email_verifications.user_id
target: auth.users.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: auth.phone_verifications.user_id
target: auth.users.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: auth.password_reset_tokens.user_id
target: auth.users.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: auth.notifications.user_id
target: auth.users.id
cross_schema: false
on_delete: CASCADE
valid: true
notes: "Inline REFERENCES syntax"
- source: auth.user_push_tokens.user_id
target: auth.users.id
cross_schema: false
on_delete: CASCADE
valid: true
notes: "Inline REFERENCES syntax"
# ---- TRADING SCHEMA ----
- source: trading.watchlists.user_id
target: auth.users.id
cross_schema: true
on_delete: CASCADE
valid: true
- source: trading.watchlist_items.watchlist_id
target: trading.watchlists.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: trading.watchlist_items.symbol_id
target: trading.symbols.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: trading.bots.user_id
target: auth.users.id
cross_schema: true
on_delete: CASCADE
valid: true
- source: trading.orders.user_id
target: auth.users.id
cross_schema: true
on_delete: CASCADE
valid: true
- source: trading.orders.bot_id
target: trading.bots.id
cross_schema: false
on_delete: SET NULL
valid: true
- source: trading.orders.symbol_id
target: trading.symbols.id
cross_schema: false
on_delete: NO ACTION
valid: true
notes: "No ON DELETE specified - defaults to NO ACTION"
- source: trading.positions.user_id
target: auth.users.id
cross_schema: true
on_delete: CASCADE
valid: true
- source: trading.positions.bot_id
target: trading.bots.id
cross_schema: false
on_delete: SET NULL
valid: true
- source: trading.positions.symbol_id
target: trading.symbols.id
cross_schema: false
on_delete: NO ACTION
valid: true
notes: "No ON DELETE specified - defaults to NO ACTION"
- source: trading.trades.order_id
target: trading.orders.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: trading.trades.position_id
target: trading.positions.id
cross_schema: false
on_delete: SET NULL
valid: true
- source: trading.trading_metrics.bot_id
target: trading.bots.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: trading.paper_balances.user_id
target: auth.users.id
cross_schema: true
on_delete: CASCADE
valid: true
- source: trading.price_alerts.user_id
target: auth.users.id
cross_schema: true
on_delete: CASCADE
valid: true
- source: trading.price_alerts.symbol_id
target: trading.symbols.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: trading.drawing_tools.user_id
target: auth.users.id
cross_schema: true
on_delete: CASCADE
valid: true
- source: trading.drawing_tools.symbol_id
target: trading.symbols.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: trading.drawing_templates.user_id
target: auth.users.id
cross_schema: true
on_delete: CASCADE
valid: true
notes: "user_id is NULLable (NULL = system template)"
# ---- EDUCATION SCHEMA ----
- source: education.categories.parent_id
target: education.categories.id
cross_schema: false
on_delete: SET NULL
valid: true
notes: "Self-referencing FK for hierarchy"
- source: education.courses.category_id
target: education.categories.id
cross_schema: false
on_delete: RESTRICT
valid: true
- source: education.courses.instructor_id
target: auth.users.id
cross_schema: true
on_delete: RESTRICT
valid: true
notes: "Should eventually reference education.instructors(user_id) - migration pending"
- source: education.modules.course_id
target: education.courses.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: education.modules.unlock_after_module_id
target: education.modules.id
cross_schema: false
on_delete: SET NULL
valid: true
notes: "Self-referencing FK for module ordering"
- source: education.lessons.module_id
target: education.modules.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: education.enrollments.user_id
target: auth.users.id
cross_schema: true
on_delete: CASCADE
valid: true
- source: education.enrollments.course_id
target: education.courses.id
cross_schema: false
on_delete: RESTRICT
valid: true
- source: education.progress.user_id
target: auth.users.id
cross_schema: true
on_delete: CASCADE
valid: true
- source: education.progress.lesson_id
target: education.lessons.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: education.progress.enrollment_id
target: education.enrollments.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: education.quizzes.module_id
target: education.modules.id
cross_schema: false
on_delete: CASCADE
valid: true
notes: "NULLable, mutual exclusion with lesson_id"
- source: education.quizzes.lesson_id
target: education.lessons.id
cross_schema: false
on_delete: CASCADE
valid: true
notes: "NULLable, mutual exclusion with module_id"
- source: education.quiz_questions.quiz_id
target: education.quizzes.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: education.quiz_attempts.user_id
target: auth.users.id
cross_schema: true
on_delete: CASCADE
valid: true
- source: education.quiz_attempts.quiz_id
target: education.quizzes.id
cross_schema: false
on_delete: RESTRICT
valid: true
- source: education.quiz_attempts.enrollment_id
target: education.enrollments.id
cross_schema: false
on_delete: SET NULL
valid: true
- source: education.certificates.user_id
target: auth.users.id
cross_schema: true
on_delete: CASCADE
valid: true
- source: education.certificates.course_id
target: education.courses.id
cross_schema: false
on_delete: RESTRICT
valid: true
- source: education.certificates.enrollment_id
target: education.enrollments.id
cross_schema: false
on_delete: RESTRICT
valid: true
- source: education.user_achievements.user_id
target: auth.users.id
cross_schema: true
on_delete: CASCADE
valid: true
- source: education.user_achievements.course_id
target: education.courses.id
cross_schema: false
on_delete: SET NULL
valid: true
notes: "NULLable"
- source: education.user_achievements.quiz_id
target: education.quizzes.id
cross_schema: false
on_delete: SET NULL
valid: true
notes: "NULLable"
- source: education.user_gamification_profile.user_id
target: auth.users.id
cross_schema: true
on_delete: CASCADE
valid: true
- source: education.user_activity_log.user_id
target: auth.users.id
cross_schema: true
on_delete: CASCADE
valid: true
- source: education.user_activity_log.course_id
target: education.courses.id
cross_schema: false
on_delete: SET NULL
valid: true
- source: education.user_activity_log.lesson_id
target: education.lessons.id
cross_schema: false
on_delete: SET NULL
valid: true
- source: education.user_activity_log.quiz_id
target: education.quizzes.id
cross_schema: false
on_delete: SET NULL
valid: true
- source: education.course_reviews.user_id
target: auth.users.id
cross_schema: true
on_delete: CASCADE
valid: true
- source: education.course_reviews.course_id
target: education.courses.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: education.course_reviews.enrollment_id
target: education.enrollments.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: education.course_reviews.approved_by
target: auth.users.id
cross_schema: true
on_delete: NO ACTION
valid: true
notes: "NULLable, no ON DELETE specified"
- source: education.videos.course_id
target: education.courses.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: education.videos.lesson_id
target: education.lessons.id
cross_schema: false
on_delete: SET NULL
valid: true
notes: "NULLable"
- source: education.videos.uploaded_by
target: auth.users.id
cross_schema: true
on_delete: RESTRICT
valid: true
- source: education.review_helpful_votes.user_id
target: auth.users.id
cross_schema: true
on_delete: CASCADE
valid: true
- source: education.review_helpful_votes.review_id
target: education.course_reviews.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: education.instructors.user_id
target: auth.users.id
cross_schema: true
on_delete: CASCADE
valid: true
- source: education.course_tag_assignments.course_id
target: education.courses.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: education.course_tag_assignments.tag_id
target: education.course_tags.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: education.course_tag_assignments.assigned_by
target: auth.users.id
cross_schema: true
on_delete: NO ACTION
valid: true
notes: "NULLable, no ON DELETE specified"
# ---- FINANCIAL SCHEMA ----
- source: financial.wallets.user_id
target: auth.users.id
cross_schema: true
on_delete: RESTRICT
valid: true
- source: financial.wallet_transactions.wallet_id
target: financial.wallets.id
cross_schema: false
on_delete: RESTRICT
valid: true
- source: financial.wallet_transactions.destination_wallet_id
target: financial.wallets.id
cross_schema: false
on_delete: RESTRICT
valid: true
notes: "NULLable, for transfers"
- source: financial.wallet_transactions.related_transaction_id
target: financial.wallet_transactions.id
cross_schema: false
on_delete: NO ACTION
valid: true
notes: "Self-referencing, NULLable, for bidirectional transfers"
- source: financial.subscriptions.user_id
target: auth.users.id
cross_schema: true
on_delete: RESTRICT
valid: true
- source: financial.invoices.user_id
target: auth.users.id
cross_schema: true
on_delete: RESTRICT
valid: true
- source: financial.invoices.subscription_id
target: financial.subscriptions.id
cross_schema: false
on_delete: SET NULL
valid: true
- source: financial.payments.user_id
target: auth.users.id
cross_schema: true
on_delete: RESTRICT
valid: true
- source: financial.payments.subscription_id
target: financial.subscriptions.id
cross_schema: false
on_delete: SET NULL
valid: true
- source: financial.payments.invoice_id
target: financial.invoices.id
cross_schema: false
on_delete: SET NULL
valid: true
- source: financial.payments.wallet_transaction_id
target: financial.wallet_transactions.id
cross_schema: false
on_delete: SET NULL
valid: true
- source: financial.wallet_audit_log.wallet_id
target: financial.wallets.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: financial.wallet_audit_log.actor_id
target: auth.users.id
cross_schema: true
on_delete: SET NULL
valid: true
notes: "NULLable"
- source: financial.wallet_audit_log.transaction_id
target: financial.wallet_transactions.id
cross_schema: false
on_delete: SET NULL
valid: true
- source: financial.wallet_limits.wallet_id
target: financial.wallets.id
cross_schema: false
on_delete: CASCADE
valid: true
notes: "NULLable, used for wallet-specific limits"
- source: financial.customers.user_id
target: auth.users.id
cross_schema: true
on_delete: CASCADE
valid: true
- source: financial.payment_methods.user_id
target: auth.users.id
cross_schema: true
on_delete: CASCADE
valid: true
- source: financial.payment_methods.customer_id
target: financial.customers.id
cross_schema: false
on_delete: SET NULL
valid: true
- source: financial.refunds.payment_id
target: financial.payments.id
cross_schema: false
on_delete: RESTRICT
valid: true
- source: financial.refunds.requested_by
target: auth.users.id
cross_schema: true
on_delete: NO ACTION
valid: true
notes: "NULLable, no ON DELETE specified"
- source: financial.refunds.approved_by
target: auth.users.id
cross_schema: true
on_delete: NO ACTION
valid: true
notes: "NULLable, no ON DELETE specified"
# ---- INVESTMENT SCHEMA ----
- source: investment.risk_questionnaire.user_id
target: auth.users.id
cross_schema: true
on_delete: CASCADE
valid: true
- source: investment.accounts.user_id
target: auth.users.id
cross_schema: true
on_delete: CASCADE
valid: true
- source: investment.accounts.product_id
target: investment.products.id
cross_schema: false
on_delete: RESTRICT
valid: true
- source: investment.accounts.questionnaire_id
target: investment.risk_questionnaire.id
cross_schema: false
on_delete: NO ACTION
valid: true
notes: "NULLable"
- source: investment.distributions.product_id
target: investment.products.id
cross_schema: false
on_delete: RESTRICT
valid: true
- source: investment.transactions.account_id
target: investment.accounts.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: investment.transactions.distribution_id
target: investment.distributions.id
cross_schema: false
on_delete: NO ACTION
valid: true
notes: "NULLable"
- source: investment.withdrawal_requests.account_id
target: investment.accounts.id
cross_schema: false
on_delete: RESTRICT
valid: true
- source: investment.withdrawal_requests.user_id
target: auth.users.id
cross_schema: true
on_delete: RESTRICT
valid: true
- source: investment.withdrawal_requests.reviewed_by
target: auth.users.id
cross_schema: true
on_delete: NO ACTION
valid: true
notes: "NULLable"
- source: investment.daily_performance.account_id
target: investment.accounts.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: investment.daily_performance.product_id
target: investment.products.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: investment.distribution_history.account_id
target: investment.accounts.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: investment.distribution_history.product_id
target: investment.products.id
cross_schema: false
on_delete: RESTRICT
valid: true
- source: investment.agent_executions.account_id
target: investment.accounts.id
cross_schema: false
on_delete: CASCADE
valid: true
# ---- PORTFOLIO SCHEMA ----
- source: portfolio.portfolios.user_id
target: auth.users.id
cross_schema: true
on_delete: CASCADE
valid: true
- source: portfolio.portfolio_allocations.portfolio_id
target: portfolio.portfolios.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: portfolio.portfolio_goals.user_id
target: auth.users.id
cross_schema: true
on_delete: CASCADE
valid: true
- source: portfolio.portfolio_goals.portfolio_id
target: portfolio.portfolios.id
cross_schema: false
on_delete: SET NULL
valid: true
- source: portfolio.rebalance_history.portfolio_id
target: portfolio.portfolios.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: portfolio.portfolio_snapshots.portfolio_id
target: portfolio.portfolios.id
cross_schema: false
on_delete: CASCADE
valid: true
# ---- ML SCHEMA ----
- source: ml.model_versions.model_id
target: ml.models.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: ml.predictions.model_id
target: ml.models.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: ml.predictions.model_version_id
target: ml.model_versions.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: ml.prediction_outcomes.prediction_id
target: ml.predictions.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: ml.llm_prediction_outcomes.prediction_id
target: ml.llm_predictions.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: ml.llm_decisions.prediction_id
target: ml.llm_predictions.id
cross_schema: false
on_delete: SET NULL
valid: true
- source: ml.prediction_overlays.prediction_id
target: ml.predictions.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: ml.backtest_runs.user_id
target: auth.users.id
cross_schema: true
on_delete: SET NULL
valid: true
# ---- LLM SCHEMA ----
- source: llm.conversations.user_id
target: auth.users.id
cross_schema: true
on_delete: CASCADE
valid: true
- source: llm.messages.conversation_id
target: llm.conversations.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: llm.user_preferences.user_id
target: auth.users.id
cross_schema: true
on_delete: CASCADE
valid: true
- source: llm.user_memory.user_id
target: auth.users.id
cross_schema: true
on_delete: CASCADE
valid: true
- source: llm.user_memory.source_conversation_id
target: llm.conversations.id
cross_schema: false
on_delete: SET NULL
valid: true
- source: llm.embeddings.user_id
target: auth.users.id
cross_schema: true
on_delete: CASCADE
valid: true
notes: "NULLable"
# ---- AUDIT SCHEMA ----
- source: audit.audit_logs.user_id
target: auth.users.id
cross_schema: true
on_delete: SET NULL
valid: true
- source: audit.security_events.user_id
target: auth.users.id
cross_schema: true
on_delete: SET NULL
valid: true
- source: audit.security_events.reviewed_by
target: auth.users.id
cross_schema: true
on_delete: NO ACTION
valid: true
- source: audit.trading_audit.user_id
target: auth.users.id
cross_schema: true
on_delete: CASCADE
valid: true
- source: audit.api_request_logs.user_id
target: auth.users.id
cross_schema: true
on_delete: SET NULL
valid: true
- source: audit.data_access_logs.accessor_user_id
target: auth.users.id
cross_schema: true
on_delete: CASCADE
valid: true
- source: audit.data_access_logs.target_user_id
target: auth.users.id
cross_schema: true
on_delete: SET NULL
valid: true
- source: audit.compliance_logs.user_id
target: auth.users.id
cross_schema: true
on_delete: SET NULL
valid: true
- source: audit.compliance_logs.reviewed_by
target: auth.users.id
cross_schema: true
on_delete: NO ACTION
valid: true
# ---- MARKET_DATA SCHEMA ----
- source: market_data.ohlcv_5m.ticker_id
target: market_data.tickers.id
cross_schema: false
on_delete: NO ACTION
valid: true
notes: "tickers.id is SERIAL (INTEGER), not UUID"
- source: market_data.ohlcv_15m.ticker_id
target: market_data.tickers.id
cross_schema: false
on_delete: NO ACTION
valid: true
notes: "tickers.id is SERIAL (INTEGER), not UUID"
# ---- FEATURE_FLAGS SCHEMA ----
- source: feature_flags.user_flags.user_id
target: auth.users.id
cross_schema: true
on_delete: CASCADE
valid: true
- source: feature_flags.user_flags.flag_id
target: feature_flags.flags.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: feature_flags.evaluations.flag_id
target: feature_flags.flags.id
cross_schema: false
on_delete: CASCADE
valid: true
- source: feature_flags.evaluations.user_id
target: auth.users.id
cross_schema: true
on_delete: SET NULL
valid: true
# ---- MISSING FKs (Logical relationships without formal FK constraints) ----
missing_fks:
- source: auth.auth_logs.user_id
target: auth.users.id
reason: "Partitioned table - FKs cannot reference partitioned tables in standard PG (or are complex)"
recommendation: "Acceptable - partitioned table limitation. Validate at application level."
severity: LOW
- source: auth.auth_logs.session_id
target: auth.sessions.id
reason: "No FK defined, logical relationship exists"
recommendation: "Acceptable for log tables - session may be deleted but logs should persist"
severity: LOW
- source: auth.login_attempts.user_id
target: auth.users.id
reason: "No FK defined. NULLable column - user_id may not be resolved at attempt time"
recommendation: "Acceptable - login attempts may happen for nonexistent users"
severity: LOW
- source: auth.rate_limiting_config.created_by_id
target: auth.users.id
reason: "No FK defined for audit columns"
recommendation: "LOW priority - add FK with ON DELETE SET NULL"
severity: LOW
- source: auth.rate_limiting_config.updated_by_id
target: auth.users.id
reason: "No FK defined for audit columns"
recommendation: "LOW priority - add FK with ON DELETE SET NULL"
severity: LOW
- source: auth.users.created_by_id
target: auth.users.id
reason: "No self-referencing FK for audit columns"
recommendation: "LOW priority - add self-referencing FK with ON DELETE SET NULL"
severity: LOW
- source: auth.users.updated_by_id
target: auth.users.id
reason: "No self-referencing FK for audit columns"
recommendation: "LOW priority - add self-referencing FK with ON DELETE SET NULL"
severity: LOW
- source: trading.signals.symbol
target: trading.symbols.symbol
reason: "Uses VARCHAR symbol name instead of UUID FK to symbols table"
recommendation: "MEDIUM - Consider adding symbol_id UUID FK for referential integrity"
severity: MEDIUM
- source: trading.trades.symbol
target: trading.symbols.symbol
reason: "Uses VARCHAR symbol name instead of UUID FK"
recommendation: "MEDIUM - Consider adding symbol_id UUID FK for consistency with orders/positions"
severity: MEDIUM
- source: ml.feature_store.symbol
target: trading.symbols.symbol
reason: "Uses VARCHAR, no FK to trading.symbols or market_data.tickers"
recommendation: "LOW - ML tables often denormalize for performance"
severity: LOW
- source: ml.llm_predictions.symbol
target: trading.symbols.symbol
reason: "Uses VARCHAR, no FK"
recommendation: "LOW - ML tables pattern"
severity: LOW
- source: ml.llm_signals.symbol
target: trading.symbols.symbol
reason: "Uses VARCHAR, no FK"
recommendation: "LOW - ML tables pattern"
severity: LOW
- source: investment.distribution_runs
target: "no user_id FK"
reason: "Distribution runs are system-level, no user association"
recommendation: "Consider adding initiated_by UUID FK"
severity: LOW
- source: market_data.ohlcv_5m_staging
target: "no ticker_id FK"
reason: "Staging table has no FK constraints"
recommendation: "Acceptable - staging tables are temporary"
severity: LOW
# ============================================================================
# B. CONSTRAINTS AUDIT
# ============================================================================
constraints_audit:
check_constraints:
auth:
- table: users
constraints:
- name: valid_email
expression: "email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$'"
meaningful: true
- name: failed_attempts_non_negative
expression: "failed_login_attempts >= 0"
meaningful: true
- name: email_verified_at_consistency
expression: "email_verified/email_verified_at pair consistency"
meaningful: true
- name: phone_verified_at_consistency
expression: "phone_verified/phone_verified_at pair consistency"
meaningful: true
- name: mfa_secret_consistency
expression: "mfa_enabled requires mfa_secret and method"
meaningful: true
- table: sessions
constraints:
- name: valid_session_dates
expression: "expires_at > created_at"
meaningful: true
- name: invalidated_consistency
expression: "is_active/invalidated_at pair consistency"
meaningful: true
- table: email_verifications
constraints:
- name: valid_expiration
expression: "expires_at > created_at"
meaningful: true
- name: verified_consistency
expression: "is_verified/verified_at pair consistency"
meaningful: true
- table: phone_verifications
constraints:
- name: valid_expiration
expression: "expires_at > created_at"
meaningful: true
- name: verified_consistency
expression: "same as email"
meaningful: true
- name: valid_attempts
expression: "attempts >= 0, attempts <= max_attempts"
meaningful: true
- table: password_reset_tokens
constraints:
- name: valid_expiration
expression: "expires_at > created_at"
meaningful: true
- name: used_consistency
expression: "is_used/used_at pair consistency"
meaningful: true
- table: login_attempts
constraints:
- name: login_attempt_has_identifier
expression: "email IS NOT NULL OR user_id IS NOT NULL"
meaningful: true
- name: failure_reason_consistency
expression: "failure requires reason, success has no reason"
meaningful: true
- table: rate_limiting_config
constraints:
- name: valid_rate_limits
expression: "max_requests > 0, window_seconds > 0"
meaningful: true
- name: valid_scope
expression: "scope IN ('ip','user','email','global')"
meaningful: true
- table: notifications
constraints:
- name: valid_priority
expression: "IN ('low','normal','high','urgent')"
meaningful: true
- name: valid_type
expression: "CHECK on notification types"
meaningful: true
- name: valid_icon_type
expression: "IN ('success','warning','error','info')"
meaningful: true
- table: user_push_tokens
constraints:
- name: valid_platform
expression: "IN ('web','ios','android')"
meaningful: true
trading:
- table: paper_balances
constraints:
- name: chk_balance_consistency
expression: "total = available + locked"
meaningful: true
- name: chk_balance_non_negative
expression: "total >= 0, available >= 0, locked >= 0"
meaningful: true
- name: chk_initial_positive
expression: "initial_balance > 0"
meaningful: true
- table: price_alerts
constraints:
- name: chk_target_price_positive
expression: "target_price > 0 or NULL"
meaningful: true
- name: chk_percent_threshold_valid
expression: "0-100 range or NULL"
meaningful: true
- name: chk_alert_config
expression: "price types need price, percent types need threshold"
meaningful: true
- table: drawing_tools
constraints:
- name: drawing_tools_points_not_empty
expression: "jsonb_array_length(points) >= 1"
meaningful: true
- table: drawing_templates
constraints:
- name: drawing_templates_name_not_empty
expression: "length(trim(name)) > 0"
meaningful: true
education:
- table: categories
constraints:
- name: valid_color_format
expression: "color ~ '^#[0-9A-Fa-f]{6}$'"
meaningful: true
- table: courses
constraints:
- name: valid_rating
expression: "avg_rating >= 0 AND <= 5"
meaningful: true
- name: valid_price
expression: "price_usd >= 0"
meaningful: true
- table: enrollments
constraints:
- name: valid_progress
expression: "0-100"
meaningful: true
- name: valid_completion
expression: "completed requires completed_at and 100%"
meaningful: true
- table: progress
constraints:
- name: valid_watch_percentage
expression: "0-100"
meaningful: true
- name: completion_requires_date
expression: "is_completed requires completed_at"
meaningful: true
- table: quizzes
constraints:
- name: valid_passing_score
expression: "1-100"
meaningful: true
- name: quiz_association
expression: "XOR: module_id or lesson_id"
meaningful: true
- table: quiz_questions
constraints:
- name: valid_options
expression: "options required for MC/TF/MS types"
meaningful: true
- table: quiz_attempts
constraints:
- name: valid_score_percentage
expression: "0-100"
meaningful: true
- table: course_reviews
constraints:
- name: "(inline) rating CHECK"
expression: "rating >= 1 AND rating <= 5"
meaningful: true
- table: videos
constraints:
- name: valid_status
expression: "IN list of statuses"
meaningful: true
- name: valid_storage_provider
expression: "IN ('s3','r2','cloudflare_stream')"
meaningful: true
- name: valid_progress
expression: "0-100"
meaningful: true
- name: positive_duration
expression: "duration_seconds > 0 or NULL"
meaningful: true
- name: positive_file_size
expression: "file_size_bytes > 0"
meaningful: true
- table: user_gamification_profile
constraints:
- name: valid_level
expression: "current_level >= 1"
meaningful: true
- name: valid_xp
expression: "total_xp >= 0"
meaningful: true
- name: valid_streak
expression: ">= 0 for both streak fields"
meaningful: true
- name: valid_avg_score
expression: "0-100"
meaningful: true
- table: course_tags
constraints:
- name: course_tags_name_not_empty
expression: "length(trim(name)) > 0"
meaningful: true
- name: course_tags_slug_format
expression: "slug ~ '^[a-z0-9-]+$'"
meaningful: true
- name: course_tags_color_format
expression: "hex color format"
meaningful: true
- name: course_tags_usage_positive
expression: "usage_count >= 0"
meaningful: true
- table: lessons
constraints:
- name: video_fields_required
expression: "video type needs url and duration"
meaningful: true
financial:
total_check_constraints: 30
notable:
- "wallets: 6 constraints (balance equation, positivity, limits, status)"
- "wallet_transactions: 7 constraints (amount, fee, transfer, status)"
- "subscriptions: 7 constraints (price, dates, cancel, schedule)"
- "invoices: 8 constraints (amounts, dates, status)"
- "payments: 6 constraints (amount, refund, status)"
- "refunds: 2 constraints (approval, failure)"
- "wallet_limits: 4 constraints (XOR scope, min/max)"
assessment: "Excellent constraint coverage - most comprehensive in the project"
investment:
notable:
- "risk_questionnaire: score 0-100"
- "withdrawal_requests: positive amount, valid fee"
- "daily_performance: valid balances, movements, win_rate"
- "distribution_history: positive amounts, balance validation"
- "distribution_runs: valid counts"
portfolio:
notable:
- "portfolio_allocations: target_percent 0-100, current_percent 0-100"
- "portfolio_goals: target > 0, current >= 0, progress 0-100"
ml:
notable:
- "llm_decisions: 6 CHECK constraints for types, actions, risk levels"
- "llm_predictions: 5 CHECK constraints for directions, phases, levels"
- "llm_prediction_outcomes: 4 CHECK constraints for outcome validation"
- "backtest_runs: date range, config format"
missing_constraints:
- table: auth.notifications
missing: "read_at consistency (is_read=true AND read_at IS NOT NULL)"
severity: MEDIUM
- table: trading.bots
missing: "CHECK on max_position_size_pct (0-100), max_daily_loss_pct (0-100), max_drawdown_pct (0-100)"
severity: MEDIUM
- table: trading.bots
missing: "CHECK that current_capital >= 0"
severity: HIGH
- table: trading.orders
missing: "CHECK that quantity > 0, remaining_quantity >= 0"
severity: HIGH
- table: trading.orders
missing: "CHECK consistency: filled_at NOT NULL when status = 'filled'"
severity: MEDIUM
- table: trading.positions
missing: "CHECK that entry_price > 0, entry_quantity > 0, current_quantity >= 0"
severity: HIGH
- table: trading.signals
missing: "CHECK that confidence_score between 0 and 1"
severity: MEDIUM
- table: trading.trading_metrics
missing: "CHECK win_rate 0-100"
severity: LOW
- table: education.instructors
missing: "verified_at consistency with is_verified"
severity: LOW
- table: market_data.tickers
missing: "No constraints on symbol format"
severity: LOW
unique_constraints:
- auth.users.email (UNIQUE)
- auth.user_profiles.user_id (UNIQUE)
- auth.oauth_accounts.(user_id, provider) (UNIQUE)
- auth.oauth_accounts.(provider, provider_account_id) (UNIQUE)
- auth.sessions.session_token (UNIQUE)
- auth.email_verifications.token (UNIQUE)
- auth.password_reset_tokens.token (UNIQUE)
- auth.rate_limiting_config.endpoint (UNIQUE)
- auth.user_push_tokens.token (UNIQUE)
- trading.symbols.symbol (UNIQUE)
- trading.watchlists.(user_id, name) (UNIQUE)
- trading.watchlist_items.(watchlist_id, symbol_id) (UNIQUE)
- trading.trading_metrics.(bot_id, metric_date) (UNIQUE)
- trading.paper_balances.(user_id, asset) (UNIQUE)
- education.categories.slug (UNIQUE)
- education.courses.slug (UNIQUE)
- education.modules.(course_id, display_order) (UNIQUE)
- education.lessons.(module_id, display_order) (UNIQUE)
- education.enrollments.(user_id, course_id) (UNIQUE)
- education.progress.(user_id, lesson_id) (UNIQUE)
- education.certificates.certificate_number (UNIQUE)
- education.certificates.verification_code (UNIQUE)
- education.certificates.(user_id, course_id) (UNIQUE)
- education.user_gamification_profile.user_id (UNIQUE)
- education.course_reviews.(user_id, course_id) (UNIQUE)
- education.review_helpful_votes.(user_id, review_id) (UNIQUE)
- education.course_tags.slug (UNIQUE)
- education.course_tag_assignments.(course_id, tag_id) (UNIQUE)
- financial.wallets.(user_id, wallet_type, currency) (UNIQUE)
- financial.wallet_transactions.idempotency_key (UNIQUE)
- financial.subscriptions.stripe_subscription_id (UNIQUE)
- financial.invoices.stripe_invoice_id (UNIQUE)
- financial.invoices.invoice_number (UNIQUE)
- financial.payments.stripe_payment_intent_id (UNIQUE)
- financial.refunds.stripe_refund_id (UNIQUE)
- financial.customers.user_id (UNIQUE)
- financial.customers.stripe_customer_id (UNIQUE)
- financial.payment_methods.stripe_payment_method_id (UNIQUE)
- investment.products.code (UNIQUE)
- ml.model_versions.(model_id, version) (UNIQUE)
- ml.prediction_outcomes.prediction_id (UNIQUE)
- ml.llm_prediction_outcomes.prediction_id (UNIQUE)
- ml.feature_store.(symbol, timeframe, timestamp) (UNIQUE)
- llm.user_preferences.user_id (UNIQUE)
- llm.user_memory.(user_id, memory_type, key) (UNIQUE)
- feature_flags.flags.code (UNIQUE)
- feature_flags.user_flags.(user_id, flag_id) (UNIQUE)
# ============================================================================
# C. FUNCTIONS INVENTORY
# ============================================================================
functions_inventory:
global:
- name: public.update_updated_at
schema: public
parameters: "()"
return_type: TRIGGER
purpose: "Unified updated_at auto-update function. Replaces schema-specific duplicates."
used_by_triggers:
- trg_drawing_tools_updated_at (trading.drawing_tools)
- trg_drawing_templates_updated_at (trading.drawing_templates)
- trg_course_tags_updated_at (education.course_tags)
orphaned: false
auth:
- name: auth.update_updated_at
schema: auth
parameters: "()"
return_type: TRIGGER
purpose: "DEPRECATED - Schema-specific updated_at function"
used_by_triggers:
- trigger_update_users_updated_at
- trigger_update_user_profiles_updated_at
- trigger_update_oauth_accounts_updated_at
- trigger_update_sessions_updated_at
- trigger_update_rate_limiting_config_updated_at
orphaned: false
notes: "Marked DEPRECATED, should migrate to public.update_updated_at()"
- name: auth.log_auth_event
schema: auth
parameters: "(auth_event_type, UUID, CITEXT, INET, TEXT, UUID, BOOLEAN, VARCHAR, JSONB)"
return_type: UUID
purpose: "Insert auth event into auth_logs table"
used_by_triggers: []
orphaned: true
notes: "Called from application code, not by any trigger"
- name: auth.cleanup_expired_sessions
schema: auth
parameters: "(INTEGER DEFAULT 1000)"
return_type: "TABLE(deleted_count INTEGER, execution_time_ms NUMERIC)"
purpose: "Batch delete expired and old inactive sessions"
used_by_triggers: []
orphaned: true
notes: "Designed for scheduled execution (pg_cron)"
- name: auth.create_user_profile
schema: auth
parameters: "()"
return_type: TRIGGER
purpose: "Auto-create user_profile on user insert"
used_by_triggers:
- trigger_create_user_profile (auth.users)
orphaned: false
trading:
- name: trading.calculate_position_pnl
schema: trading
parameters: "(UUID, DECIMAL DEFAULT NULL)"
return_type: JSONB
purpose: "Calculate realized/unrealized PnL for a position"
used_by_triggers: []
orphaned: true
notes: "Utility function called from application code"
- name: trading.update_bot_stats
schema: trading
parameters: "(UUID)"
return_type: JSONB
purpose: "Recalculate and update bot statistics from positions"
used_by_triggers: []
orphaned: true
notes: "Utility function called from application code"
- name: trading.initialize_paper_balance
schema: trading
parameters: "(UUID, DECIMAL DEFAULT 10000, VARCHAR DEFAULT 'USDT')"
return_type: UUID
purpose: "Initialize paper trading balance for user"
used_by_triggers: []
orphaned: false
notes: "Called by trading.create_user_trading_defaults()"
- name: trading.reset_paper_balance
schema: trading
parameters: "(UUID, VARCHAR DEFAULT 'USDT', DECIMAL DEFAULT NULL)"
return_type: BOOLEAN
purpose: "Reset paper trading balance to initial value"
used_by_triggers: []
orphaned: true
notes: "Utility function"
- name: trading.update_paper_balance
schema: trading
parameters: "(UUID, VARCHAR, DECIMAL, VARCHAR)"
return_type: BOOLEAN
purpose: "Update paper balance (lock/unlock/pnl/deposit/withdrawal)"
used_by_triggers: []
orphaned: true
notes: "Utility function"
- name: trading.create_user_trading_defaults
schema: trading
parameters: "()"
return_type: TRIGGER
purpose: "Create default watchlist and paper balance on user creation"
used_by_triggers: []
orphaned: true
notes: "CRITICAL - Trigger creation is COMMENTED OUT in DDL. Should be activated."
- name: trading.update_price_alerts_timestamp
schema: trading
parameters: "()"
return_type: TRIGGER
purpose: "Update price_alerts updated_at"
used_by_triggers:
- trg_price_alerts_updated_at
orphaned: false
notes: "Should use public.update_updated_at() instead (DUP)"
education:
- name: education.update_updated_at_column
schema: education
parameters: "()"
return_type: TRIGGER
purpose: "DEPRECATED - Schema-specific updated_at function"
used_by_triggers:
- update_categories_updated_at
- update_courses_updated_at
- update_modules_updated_at
- update_lessons_updated_at
- update_enrollments_updated_at
- update_progress_updated_at
- update_quizzes_updated_at
- update_quiz_questions_updated_at
- update_user_gamification_profile_updated_at
- update_course_reviews_updated_at
orphaned: false
- name: education.update_updated_at
schema: education
parameters: "()"
return_type: TRIGGER
purpose: "Another updated_at function (used by instructors)"
used_by_triggers:
- trg_instructors_updated_at
orphaned: false
notes: "Different name from update_updated_at_column - naming inconsistency"
- name: education.update_enrollment_progress
schema: education
parameters: "()"
return_type: TRIGGER
purpose: "Recalculate enrollment progress when lesson completed"
used_by_triggers:
- update_enrollment_on_progress (education.progress)
orphaned: false
- name: education.auto_complete_enrollment
schema: education
parameters: "()"
return_type: TRIGGER
purpose: "Auto-complete enrollment when progress reaches 100%"
used_by_triggers:
- auto_complete_enrollment_trigger (education.enrollments)
orphaned: false
- name: education.generate_certificate_number
schema: education
parameters: "()"
return_type: TRIGGER
purpose: "Auto-generate certificate number and verification code"
used_by_triggers:
- generate_certificate_number_trigger (education.certificates)
orphaned: false
- name: education.update_course_rating_stats
schema: education
parameters: "()"
return_type: TRIGGER
purpose: "Update avg_rating and total_reviews on course"
used_by_triggers:
- update_course_rating_on_review_insert
- update_course_rating_on_review_update
- update_course_rating_on_review_delete
orphaned: false
- name: education.update_enrollment_count
schema: education
parameters: "()"
return_type: TRIGGER
purpose: "Update total_enrollments on course"
used_by_triggers:
- update_enrollment_count_on_insert
- update_enrollment_count_on_delete
orphaned: false
- name: education.update_user_xp
schema: education
parameters: "(UUID, INTEGER)"
return_type: VOID
purpose: "Add XP, recalculate level, create level-up achievement"
used_by_triggers: []
orphaned: true
notes: "Utility function called from application code"
- name: education.update_user_streak
schema: education
parameters: "(UUID)"
return_type: VOID
purpose: "Update daily activity streak"
used_by_triggers: []
orphaned: false
notes: "Called by trigger_update_streak"
- name: education.trigger_update_streak
schema: education
parameters: "()"
return_type: TRIGGER
purpose: "Wrapper to call update_user_streak from trigger"
used_by_triggers:
- update_streak_on_activity (education.user_activity_log)
orphaned: false
- name: education.update_tag_usage_count
schema: education
parameters: "()"
return_type: TRIGGER
purpose: "Maintain denormalized usage_count in course_tags"
used_by_triggers:
- trg_course_tag_usage (education.course_tag_assignments)
orphaned: false
- name: education.update_videos_updated_at
schema: education
parameters: "()"
return_type: TRIGGER
purpose: "Updated_at for videos table"
used_by_triggers:
- trigger_update_videos_updated_at (education.videos)
orphaned: false
notes: "Another duplicated updated_at function"
- name: education.soft_delete_video
schema: education
parameters: "(UUID)"
return_type: VOID
purpose: "Soft delete a video (set deleted_at, status='deleted')"
used_by_triggers: []
orphaned: true
notes: "Utility function"
financial:
- name: financial.update_timestamp
schema: financial
parameters: "()"
return_type: TRIGGER
purpose: "DEPRECATED - Schema-specific updated_at function"
used_by_triggers:
- trigger_wallets_updated_at
- trigger_transactions_updated_at
- trigger_subscriptions_updated_at
- trigger_payments_updated_at
- trigger_invoices_updated_at
- trigger_exchange_rates_updated_at
- trigger_wallet_limits_updated_at
orphaned: false
- name: financial.generate_invoice_number
schema: financial
parameters: "()"
return_type: TRIGGER
purpose: "Auto-generate invoice number with format INV-YYYYMM-XXXXXX"
used_by_triggers:
- trigger_invoice_number (financial.invoices)
orphaned: false
- name: financial.validate_wallet_balance
schema: financial
parameters: "()"
return_type: TRIGGER
purpose: "Ensure balance = available + pending, no negatives"
used_by_triggers:
- trigger_wallet_balance_validation (financial.wallets)
orphaned: false
- name: financial.audit_wallet_status_change
schema: financial
parameters: "()"
return_type: TRIGGER
purpose: "Log wallet status changes to audit log"
used_by_triggers:
- trigger_wallet_status_audit (financial.wallets)
orphaned: false
- name: financial.protect_completed_transactions
schema: financial
parameters: "()"
return_type: TRIGGER
purpose: "Prevent modification of completed transactions"
used_by_triggers:
- trigger_protect_completed_tx (financial.wallet_transactions)
orphaned: false
- name: financial.set_payment_timestamps
schema: financial
parameters: "()"
return_type: TRIGGER
purpose: "Auto-set succeeded_at and failed_at on payments"
used_by_triggers:
- trigger_payment_timestamps (financial.payments)
orphaned: false
- name: financial.set_subscription_ended_at
schema: financial
parameters: "()"
return_type: TRIGGER
purpose: "Auto-set ended_at when subscription cancelled"
used_by_triggers:
- trigger_subscription_ended_at (financial.subscriptions)
orphaned: false
- name: financial.validate_transaction_currency
schema: financial
parameters: "()"
return_type: TRIGGER
purpose: "Ensure transaction currency matches wallet currency"
used_by_triggers:
- trigger_transaction_currency_validation (financial.wallet_transactions)
orphaned: false
- name: financial.update_wallet_balance
schema: financial
parameters: "(UUID, DECIMAL, VARCHAR, UUID, UUID, VARCHAR, TEXT, JSONB)"
return_type: TABLE
purpose: "Safely update wallet balance with audit trail"
used_by_triggers: []
orphaned: false
notes: "Called by financial.process_transaction()"
- name: financial.reserve_wallet_funds
schema: financial
parameters: "(UUID, DECIMAL, TEXT)"
return_type: TABLE
purpose: "Move funds from available to pending"
used_by_triggers: []
orphaned: true
notes: "Utility function"
- name: financial.release_wallet_funds
schema: financial
parameters: "(UUID, DECIMAL, BOOLEAN, TEXT)"
return_type: TABLE
purpose: "Release reserved funds"
used_by_triggers: []
orphaned: true
notes: "Utility function"
- name: financial.process_transaction
schema: financial
parameters: "(UUID, transaction_type, DECIMAL, currency_code, ...)"
return_type: TABLE
purpose: "Create and process wallet transactions atomically"
used_by_triggers: []
orphaned: true
notes: "Primary transaction processing function"
- name: financial.complete_transaction
schema: financial
parameters: "(UUID)"
return_type: TABLE
purpose: "Complete a pending transaction"
used_by_triggers: []
orphaned: true
notes: "Utility function"
- name: financial.get_exchange_rate
schema: financial
parameters: "(currency_code, currency_code, TIMESTAMPTZ)"
return_type: "DECIMAL(18,8)"
purpose: "Get exchange rate between currencies"
used_by_triggers: []
orphaned: false
notes: "Called by financial.convert_currency()"
- name: financial.convert_currency
schema: financial
parameters: "(DECIMAL, currency_code, currency_code, TIMESTAMPTZ)"
return_type: "DECIMAL(20,8)"
purpose: "Convert amount between currencies"
used_by_triggers: []
orphaned: true
notes: "Utility function"
- name: financial.ensure_single_default_payment_method
schema: financial
parameters: "()"
return_type: TRIGGER
purpose: "Ensure only one default payment method per user"
used_by_triggers:
- tr_ensure_single_default_payment_method (financial.payment_methods)
orphaned: false
- name: financial.check_expired_cards
schema: financial
parameters: "()"
return_type: INTEGER
purpose: "Mark expired cards - scheduled function"
used_by_triggers: []
orphaned: true
notes: "Run monthly via scheduler"
investment:
- name: investment.generate_withdrawal_request_number
schema: investment
parameters: "()"
return_type: TRIGGER
purpose: "Auto-generate withdrawal request number"
used_by_triggers:
- tr_generate_withdrawal_request_number
orphaned: false
portfolio:
- name: portfolio.update_portfolio_updated_at
schema: portfolio
parameters: "()"
return_type: TRIGGER
purpose: "Updated_at for portfolios"
used_by_triggers:
- trg_portfolios_updated_at
orphaned: false
notes: "Yet another duplicated updated_at function"
- name: portfolio.update_goal_progress
schema: portfolio
parameters: "()"
return_type: TRIGGER
purpose: "Recalculate goal progress percentage"
used_by_triggers:
- trg_portfolio_goals_progress
orphaned: false
ml:
- name: ml.calculate_llm_prediction_accuracy
schema: ml
parameters: "(VARCHAR, INTEGER)"
return_type: TABLE
purpose: "Calculate LLM prediction accuracy statistics"
used_by_triggers: []
orphaned: true
notes: "Analytics function"
- name: ml.calculate_llm_prediction_accuracy_by_phase
schema: ml
parameters: "(VARCHAR, INTEGER)"
return_type: TABLE
purpose: "Prediction accuracy by AMD phase"
used_by_triggers: []
orphaned: true
- name: ml.calculate_llm_prediction_accuracy_by_killzone
schema: ml
parameters: "(VARCHAR, INTEGER)"
return_type: TABLE
purpose: "Prediction accuracy by killzone"
used_by_triggers: []
orphaned: true
- name: ml.calculate_llm_prediction_accuracy_by_confluence
schema: ml
parameters: "(VARCHAR, INTEGER)"
return_type: TABLE
purpose: "Prediction accuracy by confluence factor count"
used_by_triggers: []
orphaned: true
- name: ml.get_active_risk_events
schema: ml
parameters: "(VARCHAR)"
return_type: TABLE
purpose: "Get active risk events for a symbol"
used_by_triggers: []
orphaned: true
- name: ml.check_circuit_breaker_status
schema: ml
parameters: "(VARCHAR)"
return_type: TABLE
purpose: "Check if circuit breaker is triggered for symbol"
used_by_triggers: []
orphaned: true
- name: ml.update_prediction_overlays_updated_at
schema: ml
parameters: "()"
return_type: TRIGGER
purpose: "Updated_at for prediction_overlays"
used_by_triggers:
- trigger_prediction_overlays_updated_at
orphaned: false
- name: ml.update_llm_signals_updated_at
schema: ml
parameters: "()"
return_type: TRIGGER
purpose: "Updated_at for llm_signals"
used_by_triggers:
- trg_llm_signals_updated_at
orphaned: false
market_data:
- name: market_data.aggregate_5m_to_15m
schema: market_data
parameters: "(VARCHAR, TIMESTAMPTZ)"
return_type: VOID
purpose: "Aggregate 5-minute OHLCV data into 15-minute candles"
used_by_triggers: []
orphaned: true
notes: "Utility function for data pipeline"
- name: market_data.aggregate_all_15m
schema: market_data
parameters: "()"
return_type: VOID
purpose: "Aggregate all tickers from 5m to 15m"
used_by_triggers: []
orphaned: true
feature_flags:
- name: feature_flags.evaluate_flag
schema: feature_flags
parameters: "(VARCHAR, UUID, JSONB)"
return_type: BOOLEAN
purpose: "Evaluate if a feature flag is enabled for a user"
used_by_triggers: []
orphaned: true
notes: "Utility function called from application"
- name: feature_flags.update_timestamp
schema: feature_flags
parameters: "()"
return_type: TRIGGER
purpose: "DEPRECATED - Schema-specific updated_at"
used_by_triggers:
- trg_flags_updated_at
- trg_user_flags_updated_at
orphaned: false
# ============================================================================
# D. TRIGGERS INVENTORY
# ============================================================================
triggers_inventory:
auth:
- name: trigger_update_users_updated_at
table: auth.users
event: BEFORE UPDATE
function: auth.update_updated_at()
naming_convention: trigger_update_*
- name: trigger_update_user_profiles_updated_at
table: auth.user_profiles
event: BEFORE UPDATE
function: auth.update_updated_at()
naming_convention: trigger_update_*
- name: trigger_update_oauth_accounts_updated_at
table: auth.oauth_accounts
event: BEFORE UPDATE
function: auth.update_updated_at()
naming_convention: trigger_update_*
- name: trigger_update_sessions_updated_at
table: auth.sessions
event: BEFORE UPDATE
function: auth.update_updated_at()
naming_convention: trigger_update_*
- name: trigger_update_rate_limiting_config_updated_at
table: auth.rate_limiting_config
event: BEFORE UPDATE
function: auth.update_updated_at()
naming_convention: trigger_update_*
- name: trigger_create_user_profile
table: auth.users
event: AFTER INSERT
function: auth.create_user_profile()
naming_convention: trigger_*
trading:
- name: trg_price_alerts_updated_at
table: trading.price_alerts
event: BEFORE UPDATE
function: trading.update_price_alerts_timestamp()
naming_convention: trg_*
- name: trg_drawing_tools_updated_at
table: trading.drawing_tools
event: BEFORE UPDATE
function: public.update_updated_at()
naming_convention: trg_*
- name: trg_drawing_templates_updated_at
table: trading.drawing_templates
event: BEFORE UPDATE
function: public.update_updated_at()
naming_convention: trg_*
- name: "(COMMENTED OUT) tr_create_user_trading_defaults"
table: auth.users
event: AFTER INSERT
function: trading.create_user_trading_defaults()
naming_convention: tr_*
notes: "COMMENTED OUT in DDL - not active"
education:
- name: update_categories_updated_at
table: education.categories
event: BEFORE UPDATE
function: education.update_updated_at_column()
naming_convention: update_*
- name: update_courses_updated_at
table: education.courses
event: BEFORE UPDATE
function: education.update_updated_at_column()
naming_convention: update_*
- name: update_modules_updated_at
table: education.modules
event: BEFORE UPDATE
function: education.update_updated_at_column()
naming_convention: update_*
- name: update_lessons_updated_at
table: education.lessons
event: BEFORE UPDATE
function: education.update_updated_at_column()
naming_convention: update_*
- name: update_enrollments_updated_at
table: education.enrollments
event: BEFORE UPDATE
function: education.update_updated_at_column()
naming_convention: update_*
- name: update_progress_updated_at
table: education.progress
event: BEFORE UPDATE
function: education.update_updated_at_column()
naming_convention: update_*
- name: update_quizzes_updated_at
table: education.quizzes
event: BEFORE UPDATE
function: education.update_updated_at_column()
naming_convention: update_*
- name: update_quiz_questions_updated_at
table: education.quiz_questions
event: BEFORE UPDATE
function: education.update_updated_at_column()
naming_convention: update_*
- name: update_user_gamification_profile_updated_at
table: education.user_gamification_profile
event: BEFORE UPDATE
function: education.update_updated_at_column()
naming_convention: update_*
- name: update_course_reviews_updated_at
table: education.course_reviews
event: BEFORE UPDATE
function: education.update_updated_at_column()
naming_convention: update_*
- name: update_enrollment_on_progress
table: education.progress
event: "AFTER INSERT OR UPDATE WHEN (NEW.is_completed = true)"
function: education.update_enrollment_progress()
naming_convention: update_*
- name: auto_complete_enrollment_trigger
table: education.enrollments
event: BEFORE UPDATE
function: education.auto_complete_enrollment()
naming_convention: "*_trigger"
- name: generate_certificate_number_trigger
table: education.certificates
event: BEFORE INSERT
function: education.generate_certificate_number()
naming_convention: "*_trigger"
- name: update_course_rating_on_review_insert
table: education.course_reviews
event: AFTER INSERT
function: education.update_course_rating_stats()
naming_convention: update_*
- name: update_course_rating_on_review_update
table: education.course_reviews
event: "AFTER UPDATE WHEN (rating or is_approved changed)"
function: education.update_course_rating_stats()
naming_convention: update_*
- name: update_course_rating_on_review_delete
table: education.course_reviews
event: AFTER DELETE
function: education.update_course_rating_stats()
naming_convention: update_*
- name: update_enrollment_count_on_insert
table: education.enrollments
event: AFTER INSERT
function: education.update_enrollment_count()
naming_convention: update_*
- name: update_enrollment_count_on_delete
table: education.enrollments
event: AFTER DELETE
function: education.update_enrollment_count()
naming_convention: update_*
- name: update_streak_on_activity
table: education.user_activity_log
event: AFTER INSERT
function: education.trigger_update_streak()
naming_convention: update_*
- name: trigger_update_videos_updated_at
table: education.videos
event: BEFORE UPDATE
function: education.update_videos_updated_at()
naming_convention: trigger_*
- name: trg_instructors_updated_at
table: education.instructors
event: BEFORE UPDATE
function: education.update_updated_at()
naming_convention: trg_*
- name: trg_course_tags_updated_at
table: education.course_tags
event: BEFORE UPDATE
function: public.update_updated_at()
naming_convention: trg_*
- name: trg_course_tag_usage
table: education.course_tag_assignments
event: AFTER INSERT OR DELETE
function: education.update_tag_usage_count()
naming_convention: trg_*
financial:
- name: trigger_wallets_updated_at
table: financial.wallets
event: BEFORE UPDATE
function: financial.update_timestamp()
naming_convention: trigger_*
- name: trigger_transactions_updated_at
table: financial.wallet_transactions
event: BEFORE UPDATE
function: financial.update_timestamp()
naming_convention: trigger_*
- name: trigger_subscriptions_updated_at
table: financial.subscriptions
event: BEFORE UPDATE
function: financial.update_timestamp()
naming_convention: trigger_*
- name: trigger_payments_updated_at
table: financial.payments
event: BEFORE UPDATE
function: financial.update_timestamp()
naming_convention: trigger_*
- name: trigger_invoices_updated_at
table: financial.invoices
event: BEFORE UPDATE
function: financial.update_timestamp()
naming_convention: trigger_*
- name: trigger_exchange_rates_updated_at
table: financial.currency_exchange_rates
event: BEFORE UPDATE
function: financial.update_timestamp()
naming_convention: trigger_*
- name: trigger_wallet_limits_updated_at
table: financial.wallet_limits
event: BEFORE UPDATE
function: financial.update_timestamp()
naming_convention: trigger_*
- name: trigger_invoice_number
table: financial.invoices
event: BEFORE INSERT
function: financial.generate_invoice_number()
naming_convention: trigger_*
- name: trigger_wallet_balance_validation
table: financial.wallets
event: BEFORE INSERT OR UPDATE
function: financial.validate_wallet_balance()
naming_convention: trigger_*
- name: trigger_wallet_status_audit
table: financial.wallets
event: BEFORE UPDATE
function: financial.audit_wallet_status_change()
naming_convention: trigger_*
- name: trigger_protect_completed_tx
table: financial.wallet_transactions
event: BEFORE UPDATE
function: financial.protect_completed_transactions()
naming_convention: trigger_*
- name: trigger_payment_timestamps
table: financial.payments
event: BEFORE UPDATE
function: financial.set_payment_timestamps()
naming_convention: trigger_*
- name: trigger_subscription_ended_at
table: financial.subscriptions
event: BEFORE UPDATE
function: financial.set_subscription_ended_at()
naming_convention: trigger_*
- name: trigger_transaction_currency_validation
table: financial.wallet_transactions
event: BEFORE INSERT
function: financial.validate_transaction_currency()
naming_convention: trigger_*
- name: tr_ensure_single_default_payment_method
table: financial.payment_methods
event: "BEFORE INSERT OR UPDATE OF is_default WHEN (NEW.is_default = TRUE)"
function: financial.ensure_single_default_payment_method()
naming_convention: tr_*
investment:
- name: tr_generate_withdrawal_request_number
table: investment.withdrawal_requests
event: BEFORE INSERT
function: investment.generate_withdrawal_request_number()
naming_convention: tr_*
- name: trg_agent_executions_updated_at
table: investment.agent_executions
event: BEFORE UPDATE
function: public.update_updated_at()
naming_convention: trg_*
portfolio:
- name: trg_portfolios_updated_at
table: portfolio.portfolios
event: BEFORE UPDATE
function: portfolio.update_portfolio_updated_at()
naming_convention: trg_*
- name: trg_portfolio_allocations_updated_at
table: portfolio.portfolio_allocations
event: BEFORE UPDATE
function: public.update_updated_at()
naming_convention: trg_*
- name: trg_portfolio_goals_updated_at
table: portfolio.portfolio_goals
event: BEFORE UPDATE
function: public.update_updated_at()
naming_convention: trg_*
- name: trg_portfolio_goals_progress
table: portfolio.portfolio_goals
event: BEFORE UPDATE
function: portfolio.update_goal_progress()
naming_convention: trg_*
ml:
- name: trigger_prediction_overlays_updated_at
table: ml.prediction_overlays
event: BEFORE UPDATE
function: ml.update_prediction_overlays_updated_at()
naming_convention: trigger_*
- name: trg_llm_signals_updated_at
table: ml.llm_signals
event: BEFORE UPDATE
function: ml.update_llm_signals_updated_at()
naming_convention: trg_*
feature_flags:
- name: trg_flags_updated_at
table: feature_flags.flags
event: BEFORE UPDATE
function: feature_flags.update_timestamp()
naming_convention: trg_*
- name: trg_user_flags_updated_at
table: feature_flags.user_flags
event: BEFORE UPDATE
function: feature_flags.update_timestamp()
naming_convention: trg_*
trigger_naming_analysis:
conventions_found:
- "trigger_*: Used in auth (5), financial (14), education/videos (1), ml (1)"
- "trg_*: Used in trading (3), education/new (3), investment (1), portfolio (3), ml (1), feature_flags (2)"
- "update_*: Used in education (12)"
- "tr_*: Used in financial (1), investment (1)"
- "*_trigger: Used in education (2)"
inconsistency_level: HIGH
recommendation: "Standardize to trg_ prefix across all schemas"
# ============================================================================
# E. INTER-SCHEMA DEPENDENCY MAP
# ============================================================================
inter_schema_dependencies:
auth_dependents:
description: "All schemas that reference auth.users"
schemas:
- trading: 8 FKs
- education: 14 FKs
- financial: 8 FKs
- investment: 3 FKs
- portfolio: 2 FKs
- ml: 1 FK
- llm: 4 FKs
- audit: 9 FKs
- feature_flags: 2 FKs
total_cross_schema_fks_to_auth: 51
schema_dependency_matrix:
auth: [] # No outbound dependencies to other schemas
trading:
depends_on: [auth]
education:
depends_on: [auth]
financial:
depends_on: [auth]
investment:
depends_on: [auth]
portfolio:
depends_on: [auth]
ml:
depends_on: [auth]
llm:
depends_on: [auth]
audit:
depends_on: [auth]
market_data: [] # No cross-schema FKs
feature_flags:
depends_on: [auth]
circular_dependencies: NONE
notes: |
All schemas follow a clean star topology with auth as the central hub.
No schema depends on another non-auth schema via FK constraints.
market_data is completely isolated (no cross-schema FKs at all).
weak_relationships:
- from: trading.signals.symbol
to: trading.symbols.symbol
type: "Logical (VARCHAR match, no FK)"
recommendation: "Consider adding symbol_id FK for referential integrity"
- from: trading.trades.symbol
to: trading.symbols.symbol
type: "Logical (VARCHAR match, no FK)"
recommendation: "Add symbol_id FK - inconsistent with orders/positions which use symbol_id"
- from: trading.bots.symbols
to: trading.symbols.symbol
type: "Logical (VARCHAR[] array, no FK possible)"
recommendation: "Consider junction table trading.bot_symbols for proper FK"
- from: ml.llm_predictions.symbol
to: "trading.symbols or market_data.tickers"
type: "Logical (VARCHAR, no FK)"
recommendation: "LOW - ML often denormalizes, but consider FK to market_data.tickers"
- from: ml.feature_store.symbol
to: "trading.symbols or market_data.tickers"
type: "Logical (VARCHAR, no FK)"
recommendation: "LOW - Same as above"
- from: ml.llm_signals.symbol
to: "trading.symbols or market_data.tickers"
type: "Logical (VARCHAR, no FK)"
recommendation: "LOW - Same as above"
- from: ml.predictions.symbol
to: "trading.symbols or market_data.tickers"
type: "Logical (VARCHAR, no FK)"
recommendation: "LOW - Same as above"
- from: "investment.accounts/transactions"
to: "financial.wallets"
type: "Logical (investment wallets managed through financial.wallets)"
recommendation: "MEDIUM - Consider FK from investment.accounts to financial.wallets"
- from: education.courses.instructor_id
to: education.instructors.user_id
type: "Should reference instructors table instead of auth.users"
recommendation: "MEDIUM - Migration pending as documented in 17-instructors.sql"
schema_creation_order:
recommended:
- "1. public (00-extensions.sql, 00-global-types.sql, 00-global-functions.sql)"
- "2. 01-schemas.sql (create all schemas)"
- "3. auth (00-extensions, 01-enums, tables 01-12, functions, 99-deferred)"
- "4. trading (00-enums, tables 01-13, functions)"
- "5. education (00-enums, tables 01-19, functions)"
- "6. financial (00-enums, tables 01-11, functions, triggers, views)"
- "7. investment (00-enums, tables 01-10)"
- "8. portfolio (00-enums, tables 01-05)"
- "9. ml (00-enums, tables 01-12, functions)"
- "10. llm (00-enums, 00-extensions, tables 01-05)"
- "11. audit (00-enums, tables 01-07)"
- "12. market_data (00-enums, tables 01-04, functions)"
- "13. feature_flags (tables/01-flags.sql creates schema + tables)"
# ============================================================================
# F. ISSUES FOUND
# ============================================================================
issues_summary:
critical:
count: 2
items:
- id: CRIT-001
title: "trading.create_user_trading_defaults trigger is COMMENTED OUT"
description: |
The trigger that creates default watchlists and paper trading balances
for new users is commented out in 04-create_default_watchlist.sql.
This means new users will NOT get their default trading setup.
affected: "trading.create_user_trading_defaults() / auth.users trigger"
recommendation: "Uncomment the trigger or activate it in a post-deploy script"
- id: CRIT-002
title: "Missing CHECK constraints on trading.orders and trading.positions"
description: |
Critical financial columns lack CHECK constraints:
- orders.quantity has no > 0 check
- orders.remaining_quantity has no >= 0 check
- positions.entry_price has no > 0 check
- positions.entry_quantity has no > 0 check
- positions.current_quantity has no >= 0 check
- bots.current_capital has no >= 0 check
These could allow invalid data that corrupts PnL calculations.
affected: "trading.orders, trading.positions, trading.bots"
recommendation: "Add CHECK constraints for all amount/quantity/price columns"
high:
count: 5
items:
- id: HIGH-001
title: "5 duplicated update_updated_at functions across schemas"
description: |
Despite public.update_updated_at() existing as unified replacement,
the following schema-specific duplicates still exist AND are actively used:
- auth.update_updated_at() - 5 triggers
- education.update_updated_at_column() - 10 triggers
- education.update_updated_at() - 1 trigger (different name!)
- education.update_videos_updated_at() - 1 trigger
- financial.update_timestamp() - 7 triggers
- feature_flags.update_timestamp() - 2 triggers
- trading.update_price_alerts_timestamp() - 1 trigger
- portfolio.update_portfolio_updated_at() - 1 trigger
- ml.update_prediction_overlays_updated_at() - 1 trigger
- ml.update_llm_signals_updated_at() - 1 trigger
Only trading (drawing_tools, drawing_templates), education (course_tags),
portfolio (allocations, goals), and investment (agent_executions) use
the unified public function.
affected: "All schemas"
recommendation: "Migrate all triggers to use public.update_updated_at()"
- id: HIGH-002
title: "Trigger naming convention highly inconsistent"
description: |
5 different naming patterns found:
- trigger_* (auth, financial)
- trg_* (trading, portfolio, ml, feature_flags)
- update_* (education)
- tr_* (financial, investment)
- *_trigger (education)
affected: "All schemas"
recommendation: "Standardize to trg_{table}_{event} format"
- id: HIGH-003
title: "trading.signals and trading.trades use VARCHAR symbol instead of FK"
description: |
trading.signals.symbol and trading.trades.symbol use VARCHAR(20) for
the symbol name, while trading.orders and trading.positions properly
use symbol_id UUID FK to trading.symbols. This inconsistency means:
- No referential integrity on signals and trades
- Potential for orphaned/invalid symbol references
- Inconsistent query patterns across the trading module
affected: "trading.signals, trading.trades"
recommendation: "Add symbol_id UUID FK column and migrate data"
- id: HIGH-004
title: "market_data.tickers uses SERIAL PK while all other tables use UUID"
description: |
market_data.tickers has id SERIAL PRIMARY KEY while every other table
in the entire database uses UUID PKs. This creates a type mismatch
that complicates cross-schema queries and any future FK relationships.
affected: "market_data.tickers, market_data.ohlcv_5m, market_data.ohlcv_15m"
recommendation: "Consider migrating to UUID PK for consistency, or document the exception"
- id: HIGH-005
title: "Missing updated_at triggers on several tables"
description: |
Tables with updated_at column but NO trigger to auto-update:
- auth.notifications (no updated_at column either - only created_at)
- trading.symbols (has updated_at, no trigger)
- trading.bots (has updated_at, no trigger)
- trading.orders (has updated_at, no trigger)
- trading.positions (has updated_at, no trigger)
- trading.watchlists (has updated_at, no trigger)
- trading.watchlist_items (has updated_at, no trigger)
- trading.signals (no updated_at column)
- trading.trading_metrics (has updated_at, no trigger)
- trading.paper_balances (has updated_at, no trigger)
- investment.products (has updated_at, no trigger)
- financial.customers (has updated_at, no trigger)
- market_data.tickers (has updated_at, no trigger)
affected: "Multiple tables across trading, investment, financial, market_data"
recommendation: "Add BEFORE UPDATE triggers using public.update_updated_at()"
medium:
count: 6
items:
- id: MED-001
title: "feature_flags schema not declared in 01-schemas.sql"
description: |
All other schemas are created in 01-schemas.sql, but feature_flags
creates its own schema inline in tables/01-flags.sql.
affected: "feature_flags"
recommendation: "Add feature_flags to 01-schemas.sql for consistency"
- id: MED-002
title: "education.courses.instructor_id references auth.users instead of education.instructors"
description: |
The instructors table exists but courses still reference auth.users directly.
Migration is documented in 17-instructors.sql comments but not executed.
affected: "education.courses, education.instructors"
recommendation: "Execute the documented migration"
- id: MED-003
title: "Inconsistent ON DELETE behavior for audit columns"
description: |
Columns like approved_by, reviewed_by, requested_by across tables
have inconsistent ON DELETE behavior - some have no ON DELETE specified
(defaults to NO ACTION), others have SET NULL. Should standardize.
affected: "education.course_reviews.approved_by, financial.refunds.requested_by/approved_by, investment.withdrawal_requests.reviewed_by, audit.security_events.reviewed_by"
recommendation: "Standardize to ON DELETE SET NULL for all audit/reference columns"
- id: MED-004
title: "auth.auth_logs is partitioned but no FK to auth.users"
description: |
Partitioned tables have limitations with FKs in PostgreSQL.
auth_logs.user_id and auth_logs.session_id have no FK constraints.
While this is a known PG limitation, there is no application-level
validation documented.
affected: "auth.auth_logs"
recommendation: "Document the constraint limitation and add application-level validation"
- id: MED-005
title: "Enum naming conflicts between schemas"
description: |
financial.transaction_type and investment.transaction_type share the
same enum name. Both are marked DEPRECATED with planned rename but
migration not yet executed.
affected: "financial.transaction_type, investment.transaction_type"
recommendation: "Execute the rename migration as documented"
- id: MED-006
title: "Deprecated enum trading.timeframe still in use"
description: |
public.trading_timeframe exists as the unified replacement, but
trading.timeframe is still actively used by: trading.bots, trading.signals,
trading.drawing_tools, ml.feature_store. Migration documented but not executed.
affected: "trading.timeframe, public.trading_timeframe"
recommendation: "Execute the timeframe unification migration"
low:
count: 4
items:
- id: LOW-001
title: "auth.users audit columns (created_by_id, updated_by_id) have no FK"
description: "Self-referencing FKs not defined for audit trail columns"
recommendation: "Add FK with ON DELETE SET NULL"
- id: LOW-002
title: "No CHECK constraint on trading.bots percentage fields"
description: "max_position_size_pct, max_daily_loss_pct, max_drawdown_pct have no 0-100 range check"
recommendation: "Add CHECK constraints"
- id: LOW-003
title: "trading.signals.confidence_score lacks range constraint"
description: "Documented as 0.0000 to 1.0000 but no CHECK constraint"
recommendation: "Add CHECK (confidence_score >= 0 AND confidence_score <= 1)"
- id: LOW-004
title: "market_data.ohlcv_5m_staging has no constraints"
description: "Staging table has zero constraints - acceptable but could have basic data validation"
recommendation: "Optional - add basic NOT NULL constraints on critical columns"
total_issues:
critical: 2
high: 5
medium: 6
low: 4
total: 17