7.8 KiB
P0 DATA STRUCTURAL AUDIT REPORT: _extract_limit() & JSONB Tier Structure
Date: 2026-06-28
Author: System Architect
Scope: system.subscription_tiers, finance.org_subscriptions, _extract_limit()
Status: ✅ Structural audit complete — _extract_limit logic is correct, DB JSONB is valid
1. AUDIT: system.subscription_tiers (Base Tier Rules)
Raw JSON Structure
All three corporate tiers have their limits stored at rules["allowances"] (nested object, 2nd level):
| Tier | ID | jsonb_typeof(rules) |
allowances.max_vehicles |
allowances.max_garages |
|---|---|---|---|---|
private_pro_v1 |
14 | object ✅ |
3 | 1 |
corp_premium_v1 |
16 | object ✅ |
20 | 3 |
corp_premium_plus_v1 |
17 | object ✅ |
50 | 10 |
Full JSON for corp_premium_plus_v1 (id=17)
{
"type": "corporate",
"pricing": { "currency": "EUR", "credit_price": 6000, "yearly_price": 599.99, "monthly_price": 59.99 },
"duration": { "days": 30, "allow_stacking": true },
"ad_policy": { "show_ads": false, "ad_free_grace_days": 0, "max_daily_impressions": null },
"affiliate": { "referral_bonus_credits": 250, "commission_rate_percent": 20 },
"lifecycle": { "is_public": true },
"marketing": { "badge": "Plus", "subtitle": "Nagy flották prémium menedzsmentje.", "highlight_color": "#8B5CF6" },
"allowances": { "max_garages": 10, "max_vehicles": 50, "monthly_free_credits": 800 },
"display_name": "Ceiges Preimium Plus",
"entitlements": ["SRV_DATA_EXPORT", "SRV_AI_UPLOAD", "SRV_ACCOUNTING_SYNC"],
"pricing_zones": { "HU": { "currency": "HUF", "credit_price": 6000, "yearly_price": 239990, "monthly_price": 23990 }, "DEFAULT": { "currency": "EUR", "credit_price": 6000, "yearly_price": 599.99, "monthly_price": 59.99 } }
}
Confirmed: rules is a proper JSONB object, NOT a string
SELECT jsonb_typeof(rules) FROM system.subscription_tiers WHERE id = 17;
-- Result: "object" ✅ (not "string")
2. AUDIT: finance.org_subscriptions (Active Subs & Extra Allowances)
| org_id | Sub ID | Tier | Tier ID | extra_allowances |
|---|---|---|---|---|
| 15 | 2 | corp_premium_v1 |
16 | (not queried) |
| 43 | 30 | private_pro_v1 |
14 | {} |
| 44 | 22 | corp_premium_v1 |
16 | (not queried) |
| 45 | 21 | corp_premium_plus_v1 |
17 | (not queried) |
| 49 | 20 | corp_premium_v1 |
16 | (not queried) |
For org_id=43: extra_allowances = {} (empty JSONB object) — no extra allowances purchased.
3. TEST SCRIPT: _extract_limit() Behavior
Implementation (exact copy from admin_organizations.py:397)
The function searches at 3 levels:
- Level 1: Direct keys at rules root (
rules["max_vehicles"]) - Level 2: Nested under
rules["allowances"](rules["allowances"]["max_vehicles"]) - Level 3: Nested under
rules["limits"](rules["limits"]["max_vehicles"])
Test Results — ALL PASS ✅
| Tier | Vehicles | Branches | Users | Status |
|---|---|---|---|---|
corp_premium_plus_v1 |
50 ✅ | 10 ✅ | 0 | PASS |
corp_premium_v1 |
20 ✅ | 3 ✅ | 0 | PASS |
private_pro_v1 |
3 ✅ | 1 ✅ | 0 | PASS |
Edge Cases
| Input | Result | Expected | Status |
|---|---|---|---|
Empty dict {} |
5 (default) | 5 | ✅ |
None |
5 (default) | 5 | ✅ |
| String (not dict) | 5 (default) | 5 | ✅ |
| Direct key match | 100 | 100 | ✅ |
allowances.max_vehicles |
50 | 50 | ✅ |
limits.max_vehicles |
25 | 25 | ✅ |
Double-Encoding Simulation
When the JSON is double-encoded (string stored inside JSONB instead of object):
Input: '"{\\"allowances\\": {\\"max_vehicles\\": 50}}"'
→ json.loads() returns str, not dict
→ _extract_limit(str, ...) returns default=0 ❌
✅ Confirmed: Current DB data is NOT double-encoded. (jsonb_typeof = "object")
4. ROOT CAUSE ANALYSIS
The _extract_limit() function is NOT the problem
All tests confirm the function correctly extracts limits from the current JSONB structure. It fully supports the allowances nested format used by all tiers.
The REAL problem is likely in the code flow before _extract_limit is called
The endpoint get_organization_details() has two code paths:
Path A (line 579-664) — Uses finance.org_subscriptions (NEW system)
active_subs = sub_result.scalars().all() # Query finance.org_subscriptions
if active_subs:
base_subs = [s for s in active_subs if s.tier and s.tier.type in (None, '', 'base')]
primary_sub = base_subs_sorted[0] if base_subs_sorted else ...
if primary_sub and primary_sub.tier:
primary_rules = primary_sub.tier.rules or {} # ← THIS must work
Path B (line 665-691) — Uses fleet.organizations.subscription_tier_id (LEGACY)
elif org.subscription_tier:
rules = org.subscription_tier.rules or {} # ← LEGACY path
Potential failure scenarios (outside _extract_limit):
| Scenario | Symptom | Likelihood |
|---|---|---|
Path A → active_subs is empty (no finance.org_subscriptions row) |
Falls to Path B | Medium |
Path A → primary_sub.tier is None (FK broken, tier missing) |
Skips limit calc, subscription_summary stays None |
Low |
Path B → org.subscription_tier points to wrong tier (old FK mismatch) |
Wrong limits returned | Medium |
org.subscription_tier.rules is None (tier exists but rules column is NULL) |
{} or None → {} → default |
Low |
5. RECOMMENDATIONS
✅ Short-term: Verify runtime dataflow
-
Test with exact org_id that shows the 0 limit:
SELECT os.id, os.org_id, os.tier_id, st.name, st.rules IS NOT NULL as has_rules FROM finance.org_subscriptions os JOIN system.subscription_tiers st ON os.tier_id = st.id WHERE os.org_id = <problematic_org_id> AND os.is_active = true; -
Check if the endpoint takes Path A or Path B by inspecting logs/Debug mode.
✅ Long-term: Code improvements (separate ticket)
-
Add logging at key decision points in
get_organization_details():- Log
active_subscount - Log which path is taken (A vs B)
- Log
primary_ruleskeys and extracted limits - Log if
primary_sub.tieris None
- Log
-
Add defensive fallback — if
_extract_limitreturns 0 butruleshasallowances, log a warning:base_vehicles = _extract_limit(primary_rules, ["max_vehicles", "max_assets"], fc_fallback) if base_vehicles == 0 and primary_rules.get("allowances", {}).get("max_vehicles"): logger.warning(f"_extract_limit returned 0 despite allowances.max_vehicles={primary_rules['allowances']['max_vehicles']}")
🔍 Most Probable Root Cause
The issue is likely that the org(s) reporting 0 limits do NOT have an active row in finance.org_subscriptions (Path A), and the legacy Path B is failing because org.subscription_tier.rules is None or the old FK points to a tier that doesn't have allowances populated.
Next step: Identify the exact org_id(s) that show 0 and check their finance.org_subscriptions presence and fleet.organizations.subscription_tier_id value.
Appendix: Test Script Location
The standalone test script is at:
tests/active/test_extract_limit_standalone.py
Run with:
docker compose exec sf_api python3 /app/tests/test_extract_limit_standalone.py