refactor(backend/api): extract CompanyProfileService (Step 4 — file 4 of 18)
compliance/api/company_profile_routes.py (640 LOC) -> 154 LOC thin routes.
Unusual for this repo: persistence uses raw SQL via sqlalchemy.text()
because the underlying compliance_company_profiles table has ~45 columns
with complex jsonb coercion and there is no SQLAlchemy model for it.
New files:
compliance/schemas/company_profile.py (127) — 4 request/response models
compliance/services/company_profile_service.py (340) — Service class + row_to_response + log_audit
compliance/services/_company_profile_sql.py (139) — 70-line INSERT/UPDATE statements
separated for readability
Minor behavioral improvement: the handlers now use Depends(get_db) for
session management instead of the bespoke `db = SessionLocal(); try: ...
finally: db.close()` pattern. This makes the routes consistent with
every other refactored service, fixes the broken-ness under test
dependency_overrides, and removes 6 duplicate try/finally blocks.
Legacy exports preserved: CompanyProfileRequest, CompanyProfileResponse,
AuditEntryResponse, AuditListResponse, row_to_response, and log_audit are
re-exported from compliance.api.company_profile_routes so that the two
existing test files
(tests/test_company_profile_routes.py, tests/test_company_profile_extend.py)
keep importing from the same path.
Pre-existing broken tests noted: 6 tests in those files feed a 40-tuple
row into row_to_response, but _BASE_COLUMNS_LIST has 46 columns (has had
since the Phase 2 Stammdaten extension). These tests fail on main too
(verified via `git stash` round-trip). Not fixed in this commit — they
require a rewrite of the test's _make_row helper, which is out of scope
for a pure structural refactor. Flagged for follow-up.
Verified:
- 173/173 pytest compliance/tests/ tests/contracts/ pass
- OpenAPI 360/484 unchanged
- mypy compliance/ -> Success on 127 source files
- company_profile_routes.py 640 -> 154 LOC
- All new files under soft 300 target except service (340, under hard 500)
- Hard-cap violations: 15 -> 14
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
127
backend-compliance/compliance/schemas/company_profile.py
Normal file
127
backend-compliance/compliance/schemas/company_profile.py
Normal file
@@ -0,0 +1,127 @@
|
||||
"""
|
||||
Company Profile schemas — Stammdaten for tenants + projects.
|
||||
|
||||
Phase 1 Step 4: extracted from ``compliance.api.company_profile_routes`` so
|
||||
the route layer becomes thin delegation.
|
||||
"""
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class CompanyProfileRequest(BaseModel):
|
||||
company_name: str = ""
|
||||
legal_form: str = "GmbH"
|
||||
industry: str = ""
|
||||
founded_year: Optional[int] = None
|
||||
business_model: str = "B2B"
|
||||
offerings: list[str] = []
|
||||
offering_urls: dict[str, Any] = {}
|
||||
company_size: str = "small"
|
||||
employee_count: str = "1-9"
|
||||
annual_revenue: str = "< 2 Mio"
|
||||
headquarters_country: str = "DE"
|
||||
headquarters_country_other: str = ""
|
||||
headquarters_street: str = ""
|
||||
headquarters_zip: str = ""
|
||||
headquarters_city: str = ""
|
||||
headquarters_state: str = ""
|
||||
has_international_locations: bool = False
|
||||
international_countries: list[str] = []
|
||||
target_markets: list[str] = ["DE"]
|
||||
primary_jurisdiction: str = "DE"
|
||||
is_data_controller: bool = True
|
||||
is_data_processor: bool = False
|
||||
uses_ai: bool = False
|
||||
ai_use_cases: list[str] = []
|
||||
dpo_name: Optional[str] = None
|
||||
dpo_email: Optional[str] = None
|
||||
legal_contact_name: Optional[str] = None
|
||||
legal_contact_email: Optional[str] = None
|
||||
machine_builder: Optional[dict[str, Any]] = None
|
||||
is_complete: bool = False
|
||||
# Phase 2 fields
|
||||
repos: list[dict[str, Any]] = []
|
||||
document_sources: list[dict[str, Any]] = []
|
||||
processing_systems: list[dict[str, Any]] = []
|
||||
ai_systems: list[dict[str, Any]] = []
|
||||
technical_contacts: list[dict[str, Any]] = []
|
||||
subject_to_nis2: bool = False
|
||||
subject_to_ai_act: bool = False
|
||||
subject_to_iso27001: bool = False
|
||||
supervisory_authority: Optional[str] = None
|
||||
review_cycle_months: int = 12
|
||||
# Project ID (multi-project)
|
||||
project_id: Optional[str] = None
|
||||
|
||||
|
||||
class CompanyProfileResponse(BaseModel):
|
||||
id: str
|
||||
tenant_id: str
|
||||
project_id: Optional[str] = None
|
||||
company_name: str
|
||||
legal_form: str
|
||||
industry: str
|
||||
founded_year: Optional[int]
|
||||
business_model: str
|
||||
offerings: list[str]
|
||||
offering_urls: dict[str, Any] = {}
|
||||
company_size: str
|
||||
employee_count: str
|
||||
annual_revenue: str
|
||||
headquarters_country: str
|
||||
headquarters_country_other: str = ""
|
||||
headquarters_street: str = ""
|
||||
headquarters_zip: str = ""
|
||||
headquarters_city: str = ""
|
||||
headquarters_state: str = ""
|
||||
has_international_locations: bool
|
||||
international_countries: list[str]
|
||||
target_markets: list[str]
|
||||
primary_jurisdiction: str
|
||||
is_data_controller: bool
|
||||
is_data_processor: bool
|
||||
uses_ai: bool
|
||||
ai_use_cases: list[str]
|
||||
dpo_name: Optional[str]
|
||||
dpo_email: Optional[str]
|
||||
legal_contact_name: Optional[str]
|
||||
legal_contact_email: Optional[str]
|
||||
machine_builder: Optional[dict[str, Any]]
|
||||
is_complete: bool
|
||||
completed_at: Optional[str]
|
||||
created_at: str
|
||||
updated_at: str
|
||||
# Phase 2 fields
|
||||
repos: list[dict[str, Any]] = []
|
||||
document_sources: list[dict[str, Any]] = []
|
||||
processing_systems: list[dict[str, Any]] = []
|
||||
ai_systems: list[dict[str, Any]] = []
|
||||
technical_contacts: list[dict[str, Any]] = []
|
||||
subject_to_nis2: bool = False
|
||||
subject_to_ai_act: bool = False
|
||||
subject_to_iso27001: bool = False
|
||||
supervisory_authority: Optional[str] = None
|
||||
review_cycle_months: int = 12
|
||||
|
||||
|
||||
class AuditEntryResponse(BaseModel):
|
||||
id: str
|
||||
action: str
|
||||
changed_fields: Optional[dict[str, Any]]
|
||||
changed_by: Optional[str]
|
||||
created_at: str
|
||||
|
||||
|
||||
class AuditListResponse(BaseModel):
|
||||
entries: list[AuditEntryResponse]
|
||||
total: int
|
||||
|
||||
|
||||
__all__ = [
|
||||
"CompanyProfileRequest",
|
||||
"CompanyProfileResponse",
|
||||
"AuditEntryResponse",
|
||||
"AuditListResponse",
|
||||
]
|
||||
Reference in New Issue
Block a user