Goal progress placeholders were filtering out all goals because
start_value was missing from the SELECT statement.
Added start_value to both:
- get_active_goals() - for placeholder formatters
- get_goal_by_id() - for consistency
This will fix:
- active_goals_md progress column (was all "-")
- top_3_goals_behind_schedule (was "keine Ziele")
- top_3_goals_on_track (was "keine Ziele")
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixed 2 critical placeholder issues:
1. focus_areas_weighted_json was empty:
- Query used 'area_key' but column is 'key' in focus_area_definitions
- Changed to SELECT key, not area_key
2. Goal progress placeholders showed "nicht verfügbar":
- progress_pct in goals table is NULL (not auto-calculated)
- Added manual calculation in all 3 formatter functions:
* _format_goals_as_markdown() - shows % in table
* _format_goals_behind() - finds lowest progress
* _format_goals_on_track() - finds >= 50% progress
All placeholders should now return proper values.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
TypeError: unsupported operand type(s) for -: 'decimal.Decimal' and 'float'
PostgreSQL NUMERIC columns return Decimal objects. Must convert
current_value, target_value, start_value to float before passing
to calculate_goal_progress_pct().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixed remaining placeholder calculation issues:
1. body_progress_score returning 0:
- When start_value is NULL, query oldest weight from last 90 days
- Prevents progress = 0% when start equals current
2. focus_areas_weighted_json empty:
- Changed from goal_utils.get_focus_weights_v2() to scores.get_user_focus_weights()
- Now uses same function as focus_area_weights_json
3. Implemented 5 TODO markdown formatting functions:
- _format_goals_as_markdown() - table with progress bars
- _format_focus_areas_as_markdown() - weighted list
- _format_top_focus_areas() - top N by weight
- _format_goals_behind() - lowest progress goals
- _format_goals_on_track() - goals >= 50% progress
All placeholders should now return proper values.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root cause: Two TODO stubs always returned '[]'
Implemented:
- active_goals_json: Calls get_active_goals() from goal_utils
- focus_areas_weighted_json: Builds weighted list with names/categories
Result:
- active_goals_json now shows actual goals
- body_progress_score should calculate correctly
- top_3_goals placeholders will work
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previous commit only converted weight values, but missed:
- avg_intake (calories from DB)
- avg_protein (protein_g from DB)
- protein_per_kg calculations in loops
All DB numeric values now converted to float BEFORE arithmetic.
Fixed locations:
- Line 44: avg_intake conversion
- Line 126: avg_protein conversion
- Line 175: protein_per_kg in loop
- Line 213: protein_values list comprehension
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
TypeError: unsupported operand type(s) for *: 'decimal.Decimal' and 'float'
TypeError: unsupported operand type(s) for -: 'float' and 'decimal.Decimal'
PostgreSQL NUMERIC/DECIMAL columns return decimal.Decimal objects,
not float. These cannot be mixed in arithmetic operations.
Fixed 3 locations:
- Line 62: float(weight_row['weight']) * 32.5
- Line 153: float(weight_row['weight']) for protein_per_kg
- Line 202: float(weight_row['avg_weight']) for adequacy calc
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ImportError: cannot import name 'get_goals_by_type' from 'goal_utils'
Changes:
- body_metrics.py: Use get_active_goals() + filter by type_key
- nutrition_metrics.py: Remove unused import (dead code)
Result: Score functions no longer crash on import error.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root cause: All 3 score functions returned None because they queried
German focus area keys that don't exist in database (migration 031
uses English keys).
Changes:
- body_progress_score: körpergewicht/körperfett/muskelmasse
→ weight_loss/muscle_gain/body_recomposition
- nutrition_score: ernährung_basis/proteinzufuhr/kalorienbilanz
→ protein_intake/calorie_balance/macro_consistency/meal_timing/hydration
- activity_score: kraftaufbau/cardio/bewegungsumfang/trainingsqualität
→ strength/aerobic_endurance/flexibility/rhythm/coordination (grouped)
Result: Scores now calculate correctly with existing focus area weights.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Calculates average count per week over 30 days
- Use case: Training frequency per week (smoothed)
- Formula: (count in 30 days) / 4.285 weeks
- Documentation: .claude/docs/technical/AGGREGATION_METHODS.md
Fixed remaining sleep_log column name errors in calculate_health_stability_score:
- SELECT: total_sleep_min, deep_min, rem_min → duration_minutes, deep_minutes, rem_minutes
- _score_sleep_quality: Updated dict access to use new column names
This was blocking goal_progress_score from calculating.
Changes:
- scores.py: Fixed sleep_log SELECT query and _score_sleep_quality dict access
This should be the LAST column name bug! All Phase 0b calculations should now work.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Final bug fixes:
1. blood_pressure_log query - changed 'date' column to 'measured_at' (correct column for TIMESTAMP)
2. top_goal_name KeyError - added 'name' to SELECT in get_active_goals()
3. top_goal_name fallback - use goal_type if name is NULL
Changes:
- scores.py: Fixed blood_pressure_log query to use measured_at instead of date
- goal_utils.py: Added 'name' column to get_active_goals() SELECT
- placeholder_resolver.py: Added fallback to goal_type if name is None
These were the last 2 errors showing in logs. All major calculation bugs should now be fixed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fixed unterminated string literal in get_placeholder_catalog()
- Line 1037 had extra quote: ('quality_sessions_pct', 'Qualitätssessions (%)'),'
- Should be: ('quality_sessions_pct', 'Qualitätssessions (%)'),
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Problem: All _safe_* functions were silently catching exceptions and returning 'nicht verfügbar',
making it impossible to debug why calculations fail.
Solution: Add detailed error logging with traceback to all 4 wrapper functions:
- _safe_int(): Logs function name, exception type, message, full stack trace
- _safe_float(): Same logging
- _safe_str(): Same logging
- _safe_json(): Same logging
Now when placeholders return 'nicht verfügbar', the backend logs will show:
- Which placeholder function failed
- What exception occurred
- Full stack trace for debugging
Example log output:
[ERROR] _safe_int(goal_progress_score, uuid): ModuleNotFoundError: No module named 'calculations'
Traceback (most recent call last):
...
This will help identify if issue is:
- Missing calculations module import
- Missing data in database
- Wrong column names
- Calculation logic errors
This file was replaced by the refactored vitals system:
- vitals_baseline.py (morning measurements)
- blood_pressure.py (BP tracking with context)
Migration 015 completed the split in v9d Phase 2d.
File was no longer imported in main.py.
Cleanup result: -684 lines of dead code
Bug 1 Final Fix:
- Changed all placeholders from $1, $2, $3 to %s
- psycopg2 expects Python-style %s, converts to $N internally
- Using $N directly causes 'there is no parameter $1' error
- Removed param_idx counter (not needed with %s)
Root cause: Mixing PostgreSQL native syntax with psycopg2 driver
This is THE fix that will finally work!
Bug 3 Fix: filter_conditions was missing from SELECT statement in
list_goal_type_definitions(), preventing edit form from loading
existing filter JSON.
- Added filter_conditions to line 1087
- Now edit form correctly populates filter textarea
**Bug:** POST /api/vitals/baseline threw UndefinedParameter
**Cause:** Dynamic SQL generation had desynchronized column names and placeholders
**Fix:** Rewrote to use synchronized insert_cols, insert_placeholders, update_fields arrays
- Track param_idx correctly (start at 3 after pid and date)
- Build INSERT columns and placeholders in parallel
- Cleaner, more maintainable code
- Fixes Ruhepuls entry error
**Bug 1: Focus contributions not saved**
- GoalsPage: Added focus_contributions to data object (line 232)
- Was missing from API payload, causing loss of focus area assignments
**Bug 2: Filter focus areas in goal form**
- Only show focus areas user has weighted (weight > 0)
- Cleaner UX, avoids confusion with non-prioritized areas
- Filters focusAreasGrouped by userFocusWeights
**Bug 3: Vitals RHR entry - Internal Server Error**
- Fixed: Endpoint tried to INSERT into vitals_log (renamed in Migration 015)
- Now uses vitals_baseline table (correct post-migration table)
- Removed BP fields from baseline endpoint (use /blood-pressure instead)
- Backward compatible return format
All fixes tested and ready for production.
**Migration 032:**
- user_focus_area_weights table (profile_id, focus_area_id, weight)
- Migrates legacy 6 preferences to dynamic weights
**Backend (focus_areas.py):**
- GET /user-preferences: Returns dynamic focus weights with percentages
- PUT /user-preferences: Saves user weights (dict: focus_area_id → weight)
- Auto-calculates percentages from relative weights
- Graceful fallback if Migration 032 not applied
**Frontend (GoalsPage.jsx):**
- REMOVED: Goal Mode cards (obsolete)
- REMOVED: 6 hardcoded legacy focus sliders
- NEW: Dynamic focus area cards (weight > 0 only)
- NEW: Edit mode with sliders for all 26 areas (grouped by category)
- Clean responsive design
**How it works:**
1. Admin defines focus areas in /admin/focus-areas (26 default)
2. User sets weights for areas they care about (0-100 relative)
3. System calculates percentages automatically
4. Cards show only weighted areas
5. Goals assign to 1-n focus areas (existing functionality)
- get_focus_areas now tries user_focus_preferences first (Migration 031)
- Falls back to old focus_areas table if Migration 031 not applied
- get_goals_grouped wraps focus_contributions loading in try/catch
- Graceful degradation until migrations run
- Wrap focus_contributions loading in try/catch
- If tables don't exist (migration not run), continue without them
- Backward compatible with pre-migration state
- Logs warning but doesn't crash
- Changed prefix from '/focus-areas' to '/api/focus-areas'
- Consistent with all other routers (goals, prompts, etc.)
- Fixes 404 Not Found on /admin/focus-areas page
**Backend Safeguards:**
- get_goals_grouped: Added source_table, source_column, direction to SELECT
- create_goal_progress: Check source_table before allowing manual entry
- Returns HTTP 400 if user tries to log progress for automatic goals (weight, activity, etc.)
**Prevents:**
- Data confusion: Manual entries in goal_progress_log for weight/activity/etc.
- Dual tracking: Same data in multiple tables
- User error: Wrong data entry location
**Result:**
- Frontend filter (!goal.source_table) now works correctly
- CustomGoalsPage shows ONLY custom goals (flexibility, strength, etc.)
- Clear error message if manual entry attempted via API
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implemented progress tracking system for all goals.
**Backend:**
- Migration 030: goal_progress_log table with unique constraint per day
- Trigger: Auto-update goal.current_value from latest progress
- Endpoints: GET/POST/DELETE /api/goals/{id}/progress
- Pydantic Models: GoalProgressCreate, GoalProgressUpdate
**Features:**
- Manual progress tracking for custom goals (flexibility, strength, etc.)
- Full history with date, value, note
- current_value always reflects latest progress entry
- One entry per day per goal (unique constraint)
- Cascade delete when goal is deleted
**API:**
- GET /api/goals/{goal_id}/progress - List all entries
- POST /api/goals/{goal_id}/progress - Log new progress
- DELETE /api/goals/{goal_id}/progress/{progress_id} - Delete entry
**Next:** Frontend UI (progress button, modal, history list)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
These goal types existed but were inactive or misconfigured.
Uses UPSERT (INSERT ... ON CONFLICT DO UPDATE):
- If exists → activate + fix labels/icons/category
- If not exists → create properly
Idempotent: Safe to run multiple times, works on dev + prod.
Both types have no automatic data source (source_table = NULL),
so current_value must be updated manually.
Fixes: flexibility and strength goals not visible in admin
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
goals table doesn't have is_active column.
Removed AND g.is_active = true from WHERE clause.
Fixes: psycopg2.errors.UndefinedColumn: column g.is_active does not exist
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixed: column 'date' does not exist in blood_pressure_log
blood_pressure_log uses 'measured_at' instead of 'date'.
Added DATE_COLUMN_MAP for table-specific date columns:
- blood_pressure_log → measured_at
- fitness_tests → test_date
- all others → date
Replaced all hardcoded 'date' with dynamic date_col variable.
Fixes error: [ERROR] Failed to fetch value from blood_pressure_log.systolic
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixed SQL error: column g.linear_projection does not exist
Replaced with: g.on_track, g.projection_date (actual columns)
This was causing Internal Server Error on /api/goals/grouped
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Migration 028 failed because goals table doesn't have is_active column yet.
Removed WHERE clause from index definition.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixed health mode calculation to include all 6 dimensions.
Simplified CASE statements (single CASE instead of multiple additions).
Before: health mode only set flexibility (15%) + health (55%) = 70% ❌
After: health mode sets all dimensions = 100% ✅
- weight_loss: 5%
- muscle_gain: 0%
- strength: 10%
- endurance: 20%
- flexibility: 15%
- health: 50%
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixed health mode calculation in focus_areas migration.
Changed health_pct from 50 to 55 to ensure sum equals 100%.
Before: 0+0+10+20+15+50 = 95% (constraint violation)
After: 0+0+10+20+15+55 = 100% (valid)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
BREAKING: Replaces single 'primary goal' with weighted multi-goal system
Migration 027:
- New table: focus_areas (6 dimensions with percentages)
- Constraint: Sum must equal 100%
- Auto-migration: goal_mode → focus_areas for existing users
- Unique constraint: One active focus_areas per profile
Backend:
- get_focus_weights() V2: Reads from focus_areas table
- Fallback: Uses goal_mode if focus_areas not set
- New endpoints: GET/PUT /api/goals/focus-areas
- Validation: Sum=100, range 0-100
API:
- getFocusAreas() - Get current weights
- updateFocusAreas(data) - Update weights (upsert)
Focus dimensions:
1. weight_loss_pct (Fettabbau)
2. muscle_gain_pct (Muskelaufbau)
3. strength_pct (Kraftsteigerung)
4. endurance_pct (Ausdauer)
5. flexibility_pct (Beweglichkeit)
6. health_pct (Allgemeine Gesundheit)
Benefits:
- Multiple goals with custom priorities
- More flexible than single primary goal
- KI can use weighted scores
- Ready for Phase 0b placeholder integration
UI: Coming in next commit (slider interface)
NEW FEATURE: Filter conditions for goal types
Enables counting/aggregating specific subsets of data.
Example use case: Count only strength training sessions per week
- Create goal type with filter: {"training_type": "strength"}
- count_7d now counts only strength training, not all activities
Implementation:
- Migration 026: filter_conditions JSONB column
- Backend: Dynamic WHERE clause building from JSON filters
- Supports single value: {"training_type": "strength"}
- Supports multiple values: {"training_type": ["strength", "hiit"]}
- Works with all 8 aggregation methods (count, avg, sum, min, max)
- Frontend: JSON textarea with example + validation
- Pydantic models: filter_conditions field added
Technical details:
- SQL injection safe (parameterized queries)
- Graceful degradation (invalid JSON ignored with warning)
- Backward compatible (NULL filters = no filtering)
Answers user question: 'Kann ich Trainingstypen wie Krafttraining separat zählen?'
Answer: YES! 🎯
Admin can now easily create custom goal types:
- New endpoint /api/goals/schema-info with table/column metadata
- 9 tables documented (weight, caliper, activity, nutrition, sleep, vitals, BP, rest_days, circumference)
- Table dropdown with descriptions (e.g., 'activity_log - Trainingseinheiten')
- Column dropdown dependent on selected table
- All columns documented in German with data types
- Fields optional (for complex calculation formulas)
UX improvements:
- No need to guess table/column names
- Clear descriptions for each field
- Type-safe selection (no typos)
- Cascading dropdowns (column depends on table)
Closes user feedback: 'Admin weiß nicht welche Tabellen/Spalten verfügbar sind'
- try-catch around entire endpoint
- try-catch for each goal progress update
- Detailed error logging with traceback
- Continue processing other goals if one fails
- Clear error message to frontend
This will show exact error location in logs.
Fixes cases where Migration 024 partially ran:
- Removes created_by/updated_by columns if they exist
- Re-inserts seed data with ON CONFLICT DO NOTHING
- Fully automated, no manual intervention needed
- Production-safe (idempotent)
This ensures clean deployment to production without manual DB changes.
Goal type definitions are global system entities, not user-specific.
System types seeded in migration cannot have created_by FK.
Changes:
- Remove created_by/updated_by columns from goal_type_definitions
- Update CREATE/UPDATE endpoints to not use these fields
- Migration now runs cleanly on container start
- No manual intervention needed for production deployment
Removed faulty EXISTS check that was causing "0" error.
Added debug logging and better error messages.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Check if goal_type_definitions table exists
- Detailed error messages
- Fallback if goalTypes is empty
- Prevent form opening without types
Helps debugging Migration 024 issues.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Based on test feedback - 3 issues addressed:
1. Primary Toggle (Frontend Debug):
- Add console.log in handleSaveGoal
- Shows what data is sent to backend
- Helps debug if checkbox state is correct
2. Lean Mass Display (Backend Debug):
- Add error handling in lean_mass calculation
- Log why calculation fails (missing weight/bf data)
- Try-catch for value conversion errors
3. BP/Strength/Flexibility Warning (UI):
- Yellow warning box for incomplete goal types
- BP: "benötigt 2 Werte (geplant für v2.0)"
- Strength/Flexibility: "Keine Datenquelle"
- Transparent about limitations
Next: User re-tests with debug output to identify root cause.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The migration system tracks migrations via filename automatically.
Removed manual DO block that used wrong column name (version vs filename).
Also removed unused json import from goals.py.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- stage_debug now includes 'output' dict with all stage outputs
- Fixes empty values for stage_X_outputkey in expert mode
- Stage outputs are the actual AI responses passed to next stage
Backend:
- Add ALL stage outputs to metadata (not just referenced ones)
- Format JSON with indent for readability
- Description: 'Zwischenergebnis aus Stage X'
Frontend:
- Stage raw values shown in collapsible <details> element
- JSON formatted in <pre> tag with syntax highlighting
- 'JSON anzeigen ▼' summary for better UX
Fixes: Stage X - Rohdaten now shows intermediate results
- Each circumference point shows most recent value (even from different dates)
- Age annotations: heute, gestern, vor X Tagen/Wochen/Monaten
- Gives AI better context about measurement freshness
- Example: 'Brust 105cm (heute), Nacken 38cm (vor 2 Wochen)'
- Previously only checked c_chest, c_waist, c_hip
- Now includes c_neck, c_belly, c_thigh, c_calf, c_arm
- Fixes 'keine Daten' when entries exist with only non-primary measurements
BUG: Wertetabelle wurde nicht angezeigt
FIX: enable_debug=true wenn save=true (für metadata collection)
- metadata wird nur gespeichert wenn debug aktiv
- jetzt: debug or save → metadata immer verfügbar
BUG: {{placeholder|d}} Modifier funktionierte nicht
ROOT CAUSE: catalog wurde bei Exception nicht zu variables hinzugefügt
FIX:
- variables['_catalog'] = catalog (auch wenn None)
- Warning-Log wenn catalog nicht geladen werden kann
- Debug warning wenn |d ohne catalog verwendet
BUG: Platzhalter in Pipeline-Stages am Ende statt an Cursor
FIX:
- stageTemplateRefs Map für alle Stage-Textareas
- onClick + onKeyUp tracking für Cursor-Position
- Insert at cursor: template.slice(0, pos) + placeholder + template.slice(pos)
- Focus + Cursor restore nach Insert
TECHNICAL:
- prompt_executor.py: Besseres Exception Handling für catalog
- UnifiedPromptModal.jsx: Refs für alle Template-Felder
- prompts.py: enable_debug=debug or save
version: 9.6.1 (bugfix)
module: prompts 2.1.1
Problem: dob Spalte ist DATE (PostgreSQL) → Python bekommt datetime.date,
nicht String → strptime() schlägt fehl → age = "unbekannt"
Fix: Prüfe isinstance(dob, str) und handle beide Typen:
- String → strptime()
- date object → direkt verwenden
Jetzt funktioniert {{age}} Platzhalter korrekt.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
BREAKING: Analysis page switched from old /insights/run to new /prompts/execute
Changes:
- Backend: Added save=true parameter to /prompts/execute
- When enabled, saves final output to ai_insights table
- Extracts content from pipeline output (last stage)
- Frontend api.js: Added save parameter to executeUnifiedPrompt()
- Frontend Analysis.jsx: Switched from api.runInsight() to api.executeUnifiedPrompt()
- Transforms new result format to match InsightCard expectations
- Pipeline outputs properly extracted and displayed
Fixes: PIPELINE_MASTER responses (old template being sent to AI)
The old /insights/run endpoint used raw template field, which for the
legacy "pipeline" prompt was literally "PIPELINE_MASTER". The new
executor properly handles stages and data processing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- caliper_summary: use body_fat_pct (not bf_jpl)
- circ_summary: use c_chest, c_waist, c_hip (not brust, taille, huefte)
- get_latest_bf: use body_fat_pct for consistency
Fixes SQL errors when running base prompts that feed pipeline prompts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- New placeholder: {{activity_detail}} returns formatted activity log
- Shows last 20 activities with date, type, duration, kcal, HR
- Makes activity analysis prompts work properly
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Placeholder resolver returns keys with {{ }} wrappers,
but resolve_placeholders expects clean keys.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Backend: integrate get_placeholder_example_values in execute_prompt_with_data
- Backend: now provides BOTH raw data AND processed placeholders
- Backend: unwrap Markdown-wrapped JSON (```json ... ```)
- Fixes old-style prompts that expect name, weight_trend, caliper_summary
Resolves unresolved placeholders issue.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Frontend: debug viewer now shows even when test fails
- Frontend: export button to download complete prompt config as JSON
- Backend: attach debug info to JSON validation errors
- Backend: include raw output and length in error details
Users can now debug failed prompts and export configs for analysis.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Backend: debug mode in prompt_executor with placeholder tracking
- Backend: show resolved/unresolved placeholders, final prompts, AI responses
- Frontend: test button in UnifiedPromptModal for saved prompts
- Frontend: debug output viewer with JSON preview
- Frontend: wider placeholder example fields in PlaceholderPicker
Resolves pipeline execution debugging issues.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Issue: template has NOT NULL constraint but pipeline-type prompts
don't use template (they use stages JSONB instead).
Solution: ALTER COLUMN template DROP NOT NULL before inserting
pipeline configs into ai_prompts.
Fixed Step 3 pipeline_configs migration:
- Simplified JSONB aggregation logic
- Properly scope pc alias in subqueries
- Use UNNEST with FROM clause for array expansion
Previous version had correlation issues with nested subqueries.