refaktor címtár
This commit is contained in:
209
docs/address_api_consistency_audit.md
Normal file
209
docs/address_api_consistency_audit.md
Normal file
@@ -0,0 +1,209 @@
|
||||
# 🏗️ Cím API Végpontok Egységességi Auditja
|
||||
|
||||
**Dátum:** 2026-07-01
|
||||
**Auditor:** Rendszer-Architect
|
||||
**Cél:** Annak ellenőrzése, hogy a címet kezelő API végpontok be- és kimeneti formátuma egységes-e, és alkalmas-e egy központi címmodul (Address Module) kezelésére.
|
||||
|
||||
---
|
||||
|
||||
## 📊 Összefoglaló
|
||||
|
||||
**Verdikt: 🔴 NEM EGYSÉGES — 3 párhuzamos címformátum létezik, súlyos inkonzisztenciákkal.**
|
||||
|
||||
A rendszerben jelenleg **3 féle cím séma** és **2 teljesen eltérő címkezelési stratégia** fut párhuzamosan. Egy központi címmodul bevezetése **sürgős refaktorálást** igényel.
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Feltárt címformátumok
|
||||
|
||||
### 1. `AddressIn` / `AddressOut` (Unified P0 Refactored)
|
||||
**Fájl:** `backend/app/schemas/address.py`
|
||||
|
||||
| Mező | Típus | Leírás |
|
||||
|------|-------|--------|
|
||||
| `zip` | `Optional[str]` | Irányítószám |
|
||||
| `city` | `Optional[str]` | Város |
|
||||
| `street_name` | `Optional[str]` | Utca neve |
|
||||
| `street_type` | `Optional[str]` | Közterület jellege |
|
||||
| `house_number` | `Optional[str]` | Házszám |
|
||||
| `stairwell` | `Optional[str]` | Lépcsőház |
|
||||
| `floor` | `Optional[str]` | Emelet |
|
||||
| `door` | `Optional[str]` | Ajtó |
|
||||
| `parcel_id` | `Optional[str]` | Helyrajzi szám |
|
||||
| `full_address_text` | `Optional[str]` | Teljes cím szöveg |
|
||||
| `latitude` | `Optional[float]` | GPS szélesség |
|
||||
| `longitude` | `Optional[float]` | GPS hosszúság |
|
||||
|
||||
**Használja:** `organizations.py` (P0 refactored — nested `AddressIn` bemenet, `AddressOut` kimenet)
|
||||
|
||||
### 2. `AddressResponse` (user.py séma)
|
||||
**Fájl:** `backend/app/schemas/user.py`
|
||||
|
||||
| Mező | Típus | Eltérés |
|
||||
|------|-------|---------|
|
||||
| `address_zip` | `Optional[str]` | 🟡 **address_ előtaggal** |
|
||||
| `address_city` | `Optional[str]` | 🟡 **address_ előtaggal** |
|
||||
| `address_street_name` | `Optional[str]` | |
|
||||
| `address_street_type` | `Optional[str]` | |
|
||||
| `address_house_number` | `Optional[str]` | |
|
||||
| `address_stairwell` | `Optional[str]` | |
|
||||
| `address_floor` | `Optional[str]` | |
|
||||
| `address_door` | `Optional[str]` | |
|
||||
| `address_hrsz` | `Optional[str]` | 🟡 **eltérő név: hrsz vs parcel_id** |
|
||||
| `full_address_text` | `Optional[str]` | |
|
||||
| `latitude` | `Optional[float]` | |
|
||||
| `longitude` | `Optional[float]` | |
|
||||
|
||||
**Használja:** `users.py` (response), `admin_persons.py` (response + input: `PersonAddressUpdate`)
|
||||
|
||||
### 3. Admin endpointok saját belső sémái
|
||||
**Fájl:** `backend/app/api/v1/endpoints/admin_providers.py`
|
||||
|
||||
| Mező | Típus | Megjegyzés |
|
||||
|------|-------|------------|
|
||||
| `address` | `Optional[str]` | 🟡 **Szabad szöveges cím (legacy)** |
|
||||
| `city` | `Optional[str]` | 🟡 **city (előtag nélkül)** |
|
||||
| `address_zip` | `Optional[str]` | 🟡 **address_ előtaggal** |
|
||||
| `address_street_name` | `Optional[str]` | |
|
||||
| `address_street_type` | `Optional[str]` | |
|
||||
| `address_house_number` | `Optional[str]` | |
|
||||
| `plus_code` | `Optional[str]` | 🟢 Csak itt van |
|
||||
| `contact_phone` | `Optional[str]` | |
|
||||
|
||||
**Használja:** `admin_providers.py` (`ProviderListItem`, `ProviderDetail`, `AdminProviderUpdate`), `admin_organizations.py`
|
||||
|
||||
### 4. Admin user list (`admin.py`)
|
||||
**Fájl:** `backend/app/api/v1/endpoints/admin.py`
|
||||
|
||||
| Mező | Típus | Megjegyzés |
|
||||
|------|-------|------------|
|
||||
| `postal_code` | `Optional[str]` | 🔴 **Teljesen eltérő név** |
|
||||
| `city` | `Optional[str]` | 🟡 **előtag nélkül** |
|
||||
| `street` | `Optional[str]` | 🔴 **street (nem street_name)** |
|
||||
| `house_number` | `Optional[str]` | 🟢 Ok |
|
||||
| `address` | `Optional[str]` | 🟡 **Összefűzött string** |
|
||||
|
||||
---
|
||||
|
||||
## 🗺️ Érintett API Végpontok és Formátumuk
|
||||
|
||||
| # | Végpont fájl | Cím bemenet | Cím kimenet | Formátum azonosság |
|
||||
|---|-------------|-------------|-------------|-------------------|
|
||||
| 1 | `organizations.py` | `AddressIn` (nested) | `AddressOut` (nested) | ✅ Egységes (P0) |
|
||||
| 2 | `users.py` | Denormalizált `address_*` | `AddressResponse` (`address_*`) | ⚠️ Részben |
|
||||
| 3 | `admin.py` | — | Saját dict: `postal_code`, `city`, `street` | 🔴 **Eltérő** |
|
||||
| 4 | `admin_providers.py` | Saját `AdminProviderUpdate` | Saját `ProviderDetail` | ⚠️ Részben |
|
||||
| 5 | `admin_organizations.py` | Denormalizált `address_*` | Denormalizált `address_*` | ⚠️ Részben |
|
||||
| 6 | `admin_persons.py` | `PersonAddressUpdate` (`address_*`) | `AddressResponse` (`address_*`) | ⚠️ Részben |
|
||||
| 7 | `providers.py` | `ProviderQuickAddIn` / `ProviderUpdateIn` | `ProviderSearchResult` | ⚠️ Saját séma |
|
||||
| 8 | `gamification.py` | `city`, `address` (string) | — | 🔴 **Eltérő** |
|
||||
|
||||
---
|
||||
|
||||
## 🚨 Kritikus Inkonzisztenciák
|
||||
|
||||
### 1. 🔴 Mezőelnevezési konfliktus: `zip` vs `address_zip` vs `postal_code`
|
||||
A 3 különböző séma 3 különböző néven hivatkozik ugyanarra az adatra:
|
||||
- `zip` (`AddressIn`/`AddressOut` — P0 refactored)
|
||||
- `address_zip` (`AddressResponse`, `PersonUpdate` — user séma)
|
||||
- `postal_code` (`admin.py` user list response)
|
||||
|
||||
**Következmény:** A frontend fejlesztőknek tudniuk kell, hogy melyik endpoint melyik mezőnevet várja/adja. Ez magas hibalehetőséget hordoz.
|
||||
|
||||
### 2. 🔴 `address_hrsz` vs `parcel_id` — névütközés
|
||||
- `AddressResponse` / `PersonAddressUpdate`: `address_hrsz`
|
||||
- `AddressIn` / `AddressOut`: `parcel_id`
|
||||
- Modell (`Address`): `parcel_id`
|
||||
|
||||
**Következmény:** Ugyanaz az adat (helyrajzi szám) két különböző néven szerepel.
|
||||
|
||||
### 3. 🔴 Kétféle címkezelési stratégia (FK vs denormalizált)
|
||||
|
||||
**Stratégia A — Unified (P0 Refactored):**
|
||||
- `system.addresses` tábla + `address_id` FK
|
||||
- `AddressIn` / `AddressOut` séma
|
||||
- **Használja:** `organizations.py`
|
||||
|
||||
**Stratégia B — Denormalizált:**
|
||||
- Címmezők közvetlenül a szülő táblában (`address_city`, `address_zip`, stb.)
|
||||
- Külön `Address` entitás nélkül
|
||||
- **Használja:** `admin_organizations.py`, `admin_providers.py`, `providers.py`, `users.py`
|
||||
|
||||
### 4. 🟡 Duplikált séma definíciók
|
||||
|
||||
Az `admin_providers.py` fájlban a `ProviderListItem`, `ProviderDetail`, `AdminProviderCreate` és `AdminProviderUpdate` osztályok saját Pydantic sémákként vannak definiálva a fájlon belül. Ezek mezői részben átfednek az `AddressIn`/`AddressOut` sémákkal, de nem azokat használják.
|
||||
|
||||
### 5. 🟡 ProviderSearchResult duplikált címmezőkkel
|
||||
|
||||
A `ProviderSearchResult` (`backend/app/schemas/provider.py`) tartalmaz egy `address` (string) mezőt **és** atomizált mezőket is (`address_zip`, `address_street_name`, stb.). Ez redundáns.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Javasolt Refaktor Terv
|
||||
|
||||
### Fázis 1: Egységes címmodul megerősítése
|
||||
|
||||
Az `AddressIn`/`AddressOut` séma megtartása és kiterjesztése:
|
||||
|
||||
```python
|
||||
class AddressIn(BaseModel):
|
||||
"""Unified address input schema."""
|
||||
zip: Optional[str] = None
|
||||
city: Optional[str] = None
|
||||
street_name: Optional[str] = None
|
||||
street_type: Optional[str] = None
|
||||
house_number: Optional[str] = None
|
||||
stairwell: Optional[str] = None
|
||||
floor: Optional[str] = None
|
||||
door: Optional[str] = None
|
||||
parcel_id: Optional[str] = None
|
||||
full_address_text: Optional[str] = None
|
||||
latitude: Optional[float] = None
|
||||
longitude: Optional[float] = None
|
||||
```
|
||||
|
||||
### Fázis 2: Érintett endpointok átállítása
|
||||
|
||||
| Prioritás | Végpont | Jelenlegi | Átállás |
|
||||
|-----------|---------|-----------|---------|
|
||||
| 🔴 P1 | `admin.py` user list | `postal_code`/`city`/`street` dict | `AddressOut`-ra váltás |
|
||||
| 🔴 P1 | `admin_persons.py` | `PersonAddressUpdate` | `AddressIn` használata |
|
||||
| 🟡 P2 | `users.py` | Denormalizált `address_*` | `AddressIn` + FK |
|
||||
| 🟡 P2 | `admin_providers.py` | Saját `ProviderDetail` etc. | `AddressOut` beágyazás |
|
||||
| 🟡 P2 | `admin_organizations.py` | Denormalizált `address_*` | `AddressIn`/`AddressOut` |
|
||||
| 🟢 P3 | `providers.py` | `ProviderSearchResult` | `AddressOut` beágyazás |
|
||||
| 🟢 P3 | `gamification.py` | `city`/`address` string | `AddressIn` használata |
|
||||
|
||||
### Fázis 3: `AddressResponse` eltávolítása
|
||||
|
||||
- `AddressResponse` (`backend/app/schemas/user.py`) → eltávolítani
|
||||
- Helyette `AddressOut`-t használni mindenhol
|
||||
- Frontend kompatibilitás: mapping réteg a frontenden, ha az `address_*` előtagot várja
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Technikai Megvalósítási Javaslat
|
||||
|
||||
### Backend oldalon:
|
||||
1. `AddressIn` bővítése `country` mezővel
|
||||
2. Az összes admin endpoint átállítása `AddressIn`/`AddressOut` használatára
|
||||
3. `Organization` modell denormalizált `address_*` mezőinek deprecated flaggelése
|
||||
4. `create_or_update_address` service kiterjesztése az összes használati esetre
|
||||
|
||||
### Frontend oldalon:
|
||||
1. Egységes `address` komponens bevezetése, ami `AddressOut` formátumot vár
|
||||
2. Mapping réteg a régi `address_*` formátumról az új `AddressOut` formátumra
|
||||
|
||||
---
|
||||
|
||||
## 📈 Következtetés
|
||||
|
||||
**A rendszer JELENLEG NEM alkalmas egy központi címmodul kezelésére.**
|
||||
|
||||
A P0 refactored `AddressIn`/`AddressOut` séma irányába történő elmozdulás elkezdődött (`organizations.py`), de a többi endpoint és séma még a régi, denormalizált megközelítést használja. A kettősség komoly karbantartási terhet és hibalehetőséget jelent.
|
||||
|
||||
**Javasolt lépések:**
|
||||
1. 🔴 **Azonnal:** `admin.py` user list válasz átállítása `AddressOut` formátumra
|
||||
2. 🔴 **Rövidtávon:** `AddressResponse` (`user.py`) és `PersonAddressUpdate` (`admin_persons.py`) lecserélése `AddressOut`/`AddressIn`-re
|
||||
3. 🟡 **Középtávon:** Az összes admin provider/organization endpoint átállítása
|
||||
4. 🟢 **Hosszútávon:** Provider sémák (`ProviderSearchResult`, `ProviderQuickAddIn`) átállítása
|
||||
72
docs/p0_address_manager_usage_audit_2026-07-01.md
Normal file
72
docs/p0_address_manager_usage_audit_2026-07-01.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# P0 Architecture Audit: AddressManager Usage Verification
|
||||
|
||||
**Date:** 2026-07-01
|
||||
**Auditor:** Rendszer-Architect
|
||||
**Scope:** Backend models, services, endpoints
|
||||
**Goal:** Verify that no direct SQL/ORM inserts/updates are bypassing the AddressManager
|
||||
|
||||
---
|
||||
|
||||
## 1. Model Analysis
|
||||
|
||||
### ✅ Organization (`backend/app/models/marketplace/organization.py`)
|
||||
- **Status: CLEAN** — Zero denormalized address fields
|
||||
- Uses only FK columns: `address_id`, `billing_address_id`, `notification_address_id` → `system.addresses.id`
|
||||
- Has proper SQLAlchemy relationship definitions for all three address types
|
||||
|
||||
### ✅ Branch (`backend/app/models/marketplace/organization.py:324-366`)
|
||||
- **Status: CLEAN** — Zero denormalized address fields
|
||||
- Uses only `address_id` FK → `system.addresses.id`
|
||||
|
||||
### ✅ ServiceStaging (in `backend/app/models/marketplace/service.py:170-195`)
|
||||
- **Status: CLEAN** — Uses `address_id` FK → `system.addresses.id`
|
||||
|
||||
### ✅ Address (`backend/app/models/identity/address.py:39-74`)
|
||||
- **Status: CLEAN** — Uses proper FK-based architecture
|
||||
- `zip` and `city` are **properties** resolved via the `postal_code` relationship
|
||||
|
||||
### ⚠️ ServiceProvider (`backend/app/models/identity/social.py:23-71`)
|
||||
- **Status: INTENTIONAL DENORMALIZATION** — Contains flat address fields
|
||||
- Marked "P0 HYBRID VENDOR REFACTOR" design choice
|
||||
|
||||
---
|
||||
|
||||
## 2. Critical Bugs Found
|
||||
|
||||
### 🔴 P0 CRITICAL: `admin.py:943` — `Organization.address_city` REMOVED from ORM
|
||||
|
||||
**File:** `backend/app/api/v1/endpoints/admin.py:943`
|
||||
|
||||
The `search_organizations_by_name` endpoint selects `Organization.address_city` which is **not defined** in the `Organization` SQLAlchemy model. SQLAlchemy will raise an error at runtime.
|
||||
|
||||
**Impact:** Admin organization search (`/admin/organizations/search?name=...`) is broken.
|
||||
|
||||
### 🟡 P1: `admin_persons.py:670-699` — Direct Address writes bypassing AddressManager
|
||||
|
||||
Directly writes `address.street_name = ...` without calling `AddressManager.create_or_update()`. No `postal_code_id` resolution.
|
||||
|
||||
### 🟡 P1: `admin_organizations.py` — Flat address fields silently ignored
|
||||
|
||||
`OrganizationUpdate` schema has `address_zip`, `address_city` etc. but `hasattr(org, "address_zip")` returns False → silently ignored.
|
||||
|
||||
---
|
||||
|
||||
## 3. Database Ghost Columns
|
||||
|
||||
The following old denormalized columns **still exist** in `fleet.organizations` table but have NO ORM mapping:
|
||||
`address_city`, `address_zip`, `address_street_name`, `address_street_type`, `address_house_number`, `address_hrsz`, `billing_city/zip/street_name/street_type`, `notification_city/zip/street_name/street_type`
|
||||
|
||||
---
|
||||
|
||||
## 4. Summary
|
||||
|
||||
| Severity | Issue | File:Line |
|
||||
|----------|-------|-----------|
|
||||
| 🔴 P0 | `Organization.address_city` removed from ORM, still referenced | `admin.py:943` |
|
||||
| 🟡 P1 | Direct Address writes bypassing AddressManager | `admin_persons.py:670-699` |
|
||||
| 🟡 P1 | Flat address fields silently ignored on update | `admin_organizations.py:1242-1343` |
|
||||
| 🟡 P2 | Ghost columns in DB from old schema | `fleet.organizations` |
|
||||
| ✅ PASS | Organization model uses FKs only | `organization.py` |
|
||||
| ✅ PASS | Branch model uses FK only | `organization.py:333` |
|
||||
| ✅ PASS | admin_organizations.py correctly uses AddressManager | `admin_organizations.py:1313-1335` |
|
||||
| ✅ PASS | Address model properly normalized | `address.py` |
|
||||
65
docs/sql/cleanup_ghost_columns_2026_07.sql
Normal file
65
docs/sql/cleanup_ghost_columns_2026_07.sql
Normal file
@@ -0,0 +1,65 @@
|
||||
-- =============================================================================
|
||||
-- P0 ARCHITECTURE CLEANUP: Ghost Column Removal Script
|
||||
-- Generated: 2026-07-01
|
||||
--
|
||||
-- ⚠️ WARNING: Run this script ONLY after verifying that ALL code references
|
||||
-- to these ghost columns have been removed from the codebase.
|
||||
--
|
||||
-- These columns were previously on fleet.organizations but have been
|
||||
-- replaced by the unified address system (system.addresses FK via
|
||||
-- address_id, billing_address_id, notification_address_id).
|
||||
--
|
||||
-- The Organization model now uses Python properties that delegate to
|
||||
-- the `address` relationship (Address → GeoPostalCode).
|
||||
--
|
||||
-- Audit reference: docs/p0_address_manager_usage_audit_2026-07-01.md
|
||||
-- =============================================================================
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- ═════════════════════════════════════════════════════════════════════════════
|
||||
-- fleet.organizations — Primary Address Ghost Columns
|
||||
-- ═════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
ALTER TABLE fleet.organizations DROP COLUMN IF EXISTS address_city;
|
||||
ALTER TABLE fleet.organizations DROP COLUMN IF EXISTS address_zip;
|
||||
ALTER TABLE fleet.organizations DROP COLUMN IF EXISTS address_street_name;
|
||||
ALTER TABLE fleet.organizations DROP COLUMN IF EXISTS address_street_type;
|
||||
ALTER TABLE fleet.organizations DROP COLUMN IF EXISTS address_house_number;
|
||||
|
||||
-- ═════════════════════════════════════════════════════════════════════════════
|
||||
-- fleet.organizations — Billing Address Ghost Columns
|
||||
-- ═════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
ALTER TABLE fleet.organizations DROP COLUMN IF EXISTS billing_zip;
|
||||
ALTER TABLE fleet.organizations DROP COLUMN IF EXISTS billing_city;
|
||||
ALTER TABLE fleet.organizations DROP COLUMN IF EXISTS billing_street_name;
|
||||
ALTER TABLE fleet.organizations DROP COLUMN IF EXISTS billing_street_type;
|
||||
ALTER TABLE fleet.organizations DROP COLUMN IF EXISTS billing_house_number;
|
||||
|
||||
-- ═════════════════════════════════════════════════════════════════════════════
|
||||
-- fleet.organizations — Notification Address Ghost Columns
|
||||
-- ═════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
ALTER TABLE fleet.organizations DROP COLUMN IF EXISTS notification_zip;
|
||||
ALTER TABLE fleet.organizations DROP COLUMN IF EXISTS notification_city;
|
||||
ALTER TABLE fleet.organizations DROP COLUMN IF EXISTS notification_street_name;
|
||||
ALTER TABLE fleet.organizations DROP COLUMN IF EXISTS notification_street_type;
|
||||
ALTER TABLE fleet.organizations DROP COLUMN IF EXISTS notification_house_number;
|
||||
|
||||
-- ═════════════════════════════════════════════════════════════════════════════
|
||||
-- Verification Queries (run after migration to confirm cleanup)
|
||||
-- ═════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
-- Should return 0 rows (all ghost columns removed):
|
||||
-- SELECT column_name
|
||||
-- FROM information_schema.columns
|
||||
-- WHERE table_schema = 'fleet'
|
||||
-- AND table_name = 'organizations'
|
||||
-- AND column_name IN (
|
||||
-- 'address_city', 'address_zip', 'address_street_name', 'address_street_type', 'address_house_number',
|
||||
-- 'billing_zip', 'billing_city', 'billing_street_name', 'billing_street_type', 'billing_house_number',
|
||||
-- 'notification_zip', 'notification_city', 'notification_street_name', 'notification_street_type', 'notification_house_number'
|
||||
-- );
|
||||
|
||||
COMMIT;
|
||||
305
docs/tag_category_system_audit_report.md
Normal file
305
docs/tag_category_system_audit_report.md
Normal file
@@ -0,0 +1,305 @@
|
||||
# 🏷️ Címke/Kategória Rendszer Egységességi Audit — Backend & API
|
||||
|
||||
**Dátum:** 2026-07-01
|
||||
**Auditor:** Rendszer-Architect
|
||||
**Hatáskör:** Backend modellek, Pydantic sémák, API végpontok, service réteg
|
||||
**Cél:** A címke-kezelés (tag/category/specialization) egységességének vizsgálata
|
||||
|
||||
---
|
||||
|
||||
## 1. VEZETŐI ÖSSZEFOGLALÓ
|
||||
|
||||
A rendszerben **5 különböző címke koncepció** létezik egymás mellett, részben átfedő funkciókkal. A legkritikusabb probléma a **duális címke rendszer** (`ExpertiseTag` struktúrált fa vs. `specialization_tags` JSONB), valamint a **hiányzó `tags` mező** az admin API-ból. Az alábbi jelentés részletesen feltárja az összes anomáliát.
|
||||
|
||||
---
|
||||
|
||||
## 2. AZONOSÍTOTT CÍMKE RENDSZEREK
|
||||
|
||||
### 2.1 ExpertiseTag (4-szintű hierarchikus fa)
|
||||
| Tulajdonság | Érték |
|
||||
|---|---|
|
||||
| **Tábla** | `marketplace.expertise_tags` |
|
||||
| **Struktúra** | Adjacency List (`parent_id`) + Materialized Path (`path`) |
|
||||
| **Szintek** | Level 0 (Járműtípus) → Level 3 (Specifikus feladat) |
|
||||
| **Kötés** | `ServiceExpertise` junction táblán keresztül |
|
||||
| **Többnyelvűség** | `name_hu`, `name_en`, `name_translations` (JSONB) |
|
||||
| **Játékosítás** | `is_official`, `discovery_points`, `usage_count`, `suggested_by_id` |
|
||||
| **API végpontok** | `GET /providers/categories/tree`, `/categories/autocomplete`, `/categories` |
|
||||
|
||||
**Státusz:** ✅ Jól strukturált, DDD-konform.
|
||||
|
||||
### 2.2 ServiceProfile.specialization_tags (JSONB — strukturálatlan)
|
||||
| Tulajdonság | Érték |
|
||||
|---|---|
|
||||
| **Modell** | `ServiceProfile.specialization_tags` |
|
||||
| **Típus** | `JSONB`, server_default=`'{}'::jsonb` |
|
||||
| **Tartalom** | `{"primary_category": "str", "user_tags": ["cimke1", "cimke2"]}` |
|
||||
| **API-ban** | `ProviderSearchResult.tags: List[str]` — csak a `user_tags` érték |
|
||||
| **Service réteg** | `provider_service.py` — `spec_tags.get("user_tags", [])` |
|
||||
|
||||
**Státusz:** ⚠️ Átfedésben az ExpertiseTag rendszerrel.
|
||||
|
||||
### 2.3 ServiceProfile.specializations (JSONB — struktúrált)
|
||||
| Tulajdonság | Érték |
|
||||
|---|---|
|
||||
| **Modell** | `ServiceProfile.specializations` |
|
||||
| **Típus** | `JSONB`, server_default=`'{}'::jsonb` |
|
||||
| **Tartalom** | `{"brands": ["Volvo"], "propulsion": ["EV"]}` |
|
||||
| **Ugyanez a mező** | `ServiceProvider.specializations` — identikus struktúra |
|
||||
|
||||
**Státusz:** ✅ Konzisztens, jól elkülönített koncepció.
|
||||
|
||||
### 2.4 Organization.tags (JSONB tömb — közösségi értékelés)
|
||||
| Tulajdonság | Érték |
|
||||
|---|---|
|
||||
| **Modell** | `Organization.tags` |
|
||||
| **Típus** | `JSONB` tömb, server_default=`'[]'::jsonb` |
|
||||
| **Tartalom** | `["gyors", "megbízható", "kedvező ár"]` |
|
||||
| **API séma** | `OrganizationResponse.tags: List[str]` |
|
||||
| **Frissítés** | `OrganizationUpdate.tags: Optional[List[str]]` |
|
||||
|
||||
**Státusz:** ⚠️ Nómenklatúra ütközés a `specialization_tags` "tags" elnevezéssel.
|
||||
|
||||
### 2.5 ServiceProvider.category (Egyszerű string)
|
||||
| Tulajdonság | Érték |
|
||||
|---|---|
|
||||
| **Modell** | `ServiceProvider.category` |
|
||||
| **Típus** | `Optional[str]` |
|
||||
| **Tartalom** | Pl. `"brake_service"` — az ExpertiseTag `key` mezője |
|
||||
| **API-ban** | `ProviderSearchResult.category: Optional[str]` |
|
||||
|
||||
**Státusz:** ⚠️ Legacy mező, az új 4-level rendszer ezt fokozatosan kiváltja.
|
||||
|
||||
### 2.6 További címke/kategória mezők
|
||||
| Mező | Modell | Típus | Cél |
|
||||
|---|---|---|---|
|
||||
| `supported_vehicle_classes` | ServiceProfile, ServiceProvider | `ARRAY(String)` | Járműosztály kompatibilitás |
|
||||
| `CategoryInfo` (schema) | `provider.py` | Pydantic | API válaszban kategória adatok |
|
||||
| `SystemParameter.category` | `system.py` | `String` | Rendszerparaméter csoportosítás |
|
||||
| `Cost.category` | `service.py` | `String` | Költség kategorizálás |
|
||||
| `CostCategory` (tábla) | `fleet_finance.cost_categories` | SQL tábla | Struktúrált költségkategóriák |
|
||||
|
||||
---
|
||||
|
||||
## 3. FELTÁRT INKONZISZTENCIÁK
|
||||
|
||||
### 🔴 KRITIKUS (P0)
|
||||
|
||||
#### 3.1 Duális címke rendszer — ExpertiseTag vs specialization_tags
|
||||
|
||||
A ServiceProfile-on **KÉT** független címke tároló mező van:
|
||||
|
||||
```python
|
||||
# 1. Strukturált (FK-alapú)
|
||||
expertises: Mapped[List["ServiceExpertise"]] # → ExpertiseTag tábla
|
||||
|
||||
# 2. Strukturálatlan (JSONB)
|
||||
specialization_tags: Mapped[Any] = mapped_column(JSONB, ...) # {"user_tags": [...]}
|
||||
```
|
||||
|
||||
**Probléma:**
|
||||
- Ugyanazt a koncepciót („milyen szolgáltatást nyújt a provider?”) két különböző módon tárolja
|
||||
- A `specialization_tags.user_tags` értékei **NINCSENEK szinkronizálva** az `ExpertiseTag` táblával
|
||||
- A `quick_add_provider` és `update_provider` a `category_ids`-t az ExpertiseTag-be teszi, de a `tags`-t csak a `specialization_tags` JSONB-be
|
||||
|
||||
**Hatás:** Adatduplikáció és inkonzisztencia a két rendszer között.
|
||||
|
||||
**Javaslat:** Konszolidáció — a `specialization_tags` fokozatos kivezetése és minden címke az `ExpertiseTag` → `ServiceExpertise` kapcsolaton keresztül történjen.
|
||||
|
||||
---
|
||||
|
||||
### 🟠 MAGAS (P1)
|
||||
|
||||
#### 3.2 ProviderSearchResult.specialization — Holt mező
|
||||
|
||||
`ProviderSearchResult.specialization: List[str]` deklarálva van, de **SEHOL** sincs beállítva:
|
||||
|
||||
```python
|
||||
class ProviderSearchResult(BaseModel):
|
||||
specialization: List[str] = Field(default_factory=list) # ← MINDIG üres lista!
|
||||
```
|
||||
|
||||
A `provider_service.py` a `results.append(ProviderSearchResult(...))` hívásnál nem adja át a `specialization` paramétert, így az mindig a default `[]` értéket kapja.
|
||||
|
||||
**Hatás:** A frontend soha nem kap specializációs adatokat ezen a mezőn keresztül.
|
||||
|
||||
**Javaslat:** Távolítsd el, vagy implementáld a feltöltését.
|
||||
|
||||
---
|
||||
|
||||
#### 3.3 Admin API-ból hiányzó `tags` mező
|
||||
|
||||
A `ProviderDetail` séma **NEM tartalmaz** `tags` mezőt:
|
||||
|
||||
```python
|
||||
class ProviderDetail(BaseModel):
|
||||
category: Optional[str] = None # ✓ Van
|
||||
category_ids: Optional[List[int]] = None # ✓ Van
|
||||
supported_vehicle_classes: Optional[List[str]] = None # ✓ Van
|
||||
# tags: Optional[List[str]] = None # ✗ HIÁNYZIK!
|
||||
```
|
||||
|
||||
Ugyanez igaz a `ProviderUpdateInput` sémára is — nincs `tags` mező, pedig a user API (`ProviderUpdateIn`) tartalmazza.
|
||||
|
||||
**Hatás:** Az admin felületen nem lehet szerkeszteni a specialization_tags.user_tags értékeit.
|
||||
|
||||
**Javaslat:** Add hozzá a `tags: Optional[List[str]]` mezőt mindkét admin sémához.
|
||||
|
||||
---
|
||||
|
||||
#### 3.4 Nómenklatúra káosz
|
||||
|
||||
A rendszerben több, hasonló nevű, de eltérő koncepciójú mező van:
|
||||
|
||||
| Mező név | Hol | Típus | Koncepció |
|
||||
|---|---|---|---|
|
||||
| `specialization_tags` | ServiceProfile | JSONB dict | `{"primary_category", "user_tags"}` |
|
||||
| `specializations` | ServiceProfile | JSONB dict | `{"brands", "propulsion"}` |
|
||||
| `specialization` | ProviderSearchResult | `List[str]` | **HOLT MEZŐ** |
|
||||
| `tags` | Organization | JSONB array | Közösségi értékelő címkék |
|
||||
| `tags` | ProviderSearchResult | `List[str]` | `specialization_tags.user_tags` |
|
||||
| `tags` | ProviderQuickAddIn | `List[str]` | `specialization_tags.user_tags` |
|
||||
| `category` | ServiceProvider | `Optional[str]` | Egyszerű string |
|
||||
| `category` | ExpertiseTag | `Optional[str]` | Csoportosítás |
|
||||
| `category_ids` | Több séma | `List[int]` | ExpertiseTag ID-k |
|
||||
|
||||
**Javaslat:** Vezess be egységes nevezéktant — pl. `expertise_ids` a kategória ID-kra, `user_tags` a szabad szöveges címkékre.
|
||||
|
||||
---
|
||||
|
||||
### 🟡 KÖZEPES (P2)
|
||||
|
||||
#### 3.5 Organization.tags vs ServiceProfile.specialization_tags — Azonos név, eltérő koncepció
|
||||
|
||||
Az `Organization.tags` célja: "Crowdsourced evaluation characteristics / tags (e.g. ['gyors', 'megbízható', 'kedvező ár'])".
|
||||
|
||||
A `ServiceProfile.specialization_tags` célja: "Milyen szakmai szolgáltatást nyújt?"
|
||||
|
||||
**Mindkettőt `tags`-nek hívja a schema, de teljesen más a jelentésük.** Ez a frontend oldalán is zavart okoz.
|
||||
|
||||
---
|
||||
|
||||
#### 3.6 `new_tags` — Ingyenes string → ExpertiseTag konverzió
|
||||
|
||||
A `_create_new_tags` függvény a user által gépelt szöveges címkéket konvertálja ExpertiseTag rekordokk:
|
||||
|
||||
```python
|
||||
new_tag = ExpertiseTag(
|
||||
key=_slugify(tag_name), # pl. "gyors olajcsere" → "gyors_olajcsere"
|
||||
category="user_created", # HARDCODED!
|
||||
level=3, # HARDCODED!
|
||||
is_official=False,
|
||||
)
|
||||
```
|
||||
|
||||
**Probléma:** A `category="user_created"` hardkódolt érték, ami eltér a rendszer által használt kategória értékektől. A `level=3` szintén minden új címkére fix.
|
||||
|
||||
**Javaslat:** A `category` mezőt az API-ból (vagy ML-alapú besorolásból) kellene kapnia.
|
||||
|
||||
---
|
||||
|
||||
#### 3.7 ServiceProvider nélkülözi a `specialization_tags`-et
|
||||
|
||||
A `ServiceProvider` modellben nincs `specialization_tags` JSONB mező, csak a `ServiceProfile`-ban. Amikor a `quick_add_provider` létrehoz egy ServiceProvider-t, a `specialization_tags` adatok a ServiceProfile-ba kerülnek. Az admin lekérdezés azonban a `ProviderDetail`-ben nem adja vissza ezeket.
|
||||
|
||||
---
|
||||
|
||||
### 🔵 ALACSONY (P3)
|
||||
|
||||
#### 3.8 Cost.category vs CostCategory hivatkozás
|
||||
|
||||
A `Cost` modellben van egy `category: str` mező, de a költségek ténylegesen egy külön `CostCategory` táblára hivatkoznak `category_id`-n keresztül. Ez a kettősség redundáns.
|
||||
|
||||
#### 3.9 VehicleModelDefinition.category
|
||||
|
||||
`vehicle_definitions.py` — Egyszerű `String(50)` mező, nem kapcsolódik a 4-level rendszerhez.
|
||||
|
||||
---
|
||||
|
||||
## 4. ADATFOLYAMAT ÁBRA
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph "BEMENET (API)"
|
||||
UI1["ProviderQuickAddIn<br/>{tags, category_ids, new_tags}"]
|
||||
UI2["ProviderUpdateIn<br/>{tags, category_ids, new_tags}"]
|
||||
ADMIN["ProviderUpdateInput<br/>HIÁNYZIK: tags mező"]
|
||||
end
|
||||
|
||||
subgraph "SERVICE RÉTEG"
|
||||
QAP["quick_add_provider()"]
|
||||
UP["update_provider()"]
|
||||
SP["search_providers()"]
|
||||
end
|
||||
|
||||
subgraph "TÁROLÁS"
|
||||
ET["ExpertiseTag<br/>(4-level fa)"]
|
||||
SE["ServiceExpertise<br/>(junction)"]
|
||||
SPT["ServiceProfile<br/>.specialization_tags"]
|
||||
SPS["ServiceProfile<br/>.specializations"]
|
||||
ORG["Organization<br/>.tags (crowd)"]
|
||||
end
|
||||
|
||||
subgraph "KIMENET (API)"
|
||||
PSR["ProviderSearchResult<br/>{tags, specializations, categories}"]
|
||||
PD["ProviderDetail (admin)<br/>HIÁNYZIK: tags"]
|
||||
end
|
||||
|
||||
UI1 --> QAP
|
||||
UI2 --> UP
|
||||
ADMIN --> UP
|
||||
|
||||
QAP -->|category_ids| SE
|
||||
QAP -->|tags| SPT
|
||||
QAP -->|specializations| SPS
|
||||
QAP -->|new_tags| ET
|
||||
|
||||
UP -->|category_ids| SE
|
||||
UP -->|tags| SPT
|
||||
UP -->|specializations| SPS
|
||||
UP -->|new_tags| ET
|
||||
|
||||
SE -->|categories| PSR
|
||||
SPT -->|tags| PSR
|
||||
SPS -->|specializations| PSR
|
||||
ORG -->|tags| PSR
|
||||
|
||||
SE -.-> PD
|
||||
SPS -.->|HIÁNYZIK| PD
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. JAVASLATOK ÖSSZEFOGLALÓ
|
||||
|
||||
| Priorítás | Javaslat | Érintett fájlok |
|
||||
|---|---|---|
|
||||
| **P0** | Konszolidáld a két címke rendszert — `specialization_tags` → `ExpertiseTag` | `service.py`, `provider_service.py` |
|
||||
| **P1** | Távolítsd el vagy implementáld a `ProviderSearchResult.specialization` mezőt | `provider.py` |
|
||||
| **P1** | Add hozzá a `tags` mezőt az `AdminProviderDetail` és `ProviderUpdateInput` sémákhoz | `admin_providers.py` |
|
||||
| **P2** | Vezess be egységes nevezéktant — `expertise_ids`, `crowd_tags`, `user_tags` | Több fájl |
|
||||
| **P2** | A `_create_new_tags` ne használjon hardkódolt `category="user_created"` értéket | `provider_service.py` |
|
||||
| **P3** | Takarítsd el a `Cost.category` vs `CostCategory` duplikációt | `service.py` |
|
||||
|
||||
---
|
||||
|
||||
## 6. MELLÉKLET: TELJES MEZŐTÉRKÉP
|
||||
|
||||
| # | Mező | Modell/Schema | Típus | API-ban | Használat |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | `expertises` | ServiceProfile | `List[ServiceExpertise]` | `categories` | Strukturált kategória kapcsolat |
|
||||
| 2 | `specialization_tags` | ServiceProfile | `JSONB` | `tags` | Strukturálatlan címkék |
|
||||
| 3 | `specializations` | ServiceProfile | `JSONB` | `specializations` | Strukturált (brands, propulsion) |
|
||||
| 4 | `supported_vehicle_classes` | ServiceProfile | `ARRAY[String]` | `supported_vehicle_classes` | Jármű kompatibilitás |
|
||||
| 5 | `tags` | Organization | `JSONB[]` | `tags` | Közösségi értékelés |
|
||||
| 6 | `aliases` | Organization | `JSONB[]` | `aliases` | Alternatív nevek |
|
||||
| 7 | `category` | ServiceProvider | `Optional[str]` | `category` | Legacy string |
|
||||
| 8 | `specializations` | ServiceProvider | `JSONB` | `specializations` | Strukturált specializáció |
|
||||
| 9 | `category` | ExpertiseTag | `Optional[str]` | `category` | Címke csoport (pl. "vehicle") |
|
||||
| 10 | `category` | SystemParameter | `String` | `category` | Rendszer paraméter csoport |
|
||||
| 11 | `category` | Cost | `String` | - | Költség kategória string |
|
||||
| 12 | `category_id` | AssetCost | `Integer (FK)` | `category_id` | FK a CostCategory táblába |
|
||||
|
||||
---
|
||||
|
||||
*Audit készült: 2026-07-01 — Architect mód*
|
||||
Reference in New Issue
Block a user