Files
breakpilot-compliance/backend-compliance/compliance/schemas/company_profile.py
Sharang Parnerkar f39c7ca40c 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>
2026-04-07 19:47:29 +02:00

128 lines
3.7 KiB
Python

"""
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",
]