Files
service-finder/docs/p0_subscription_jsonb_structural_audit_report.md
2026-06-29 14:11:15 +00:00

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:

  1. Level 1: Direct keys at rules root (rules["max_vehicles"])
  2. Level 2: Nested under rules["allowances"] (rules["allowances"]["max_vehicles"])
  3. 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

  1. 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;
    
  2. Check if the endpoint takes Path A or Path B by inspecting logs/Debug mode.

Long-term: Code improvements (separate ticket)

  1. Add logging at key decision points in get_organization_details():

    • Log active_subs count
    • Log which path is taken (A vs B)
    • Log primary_rules keys and extracted limits
    • Log if primary_sub.tier is None
  2. Add defensive fallback — if _extract_limit returns 0 but rules has allowances, 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