from __future__ import annotations from datetime import date, datetime, timezone from sqlalchemy import select from sqlalchemy.orm import Session from app.modules.clients.models import Client, ClientBusinessProfile, ClientBusinessProfileSource BUSINESS_MODEL_OPTIONS = ( "manufacturing", "trading", "services", "manufacturing_and_trading", "contracting", "construction", "professional", "transport_logistics", "hospitality", "mixed", "other", ) SOURCE_TYPE_OPTIONS = ("manual_user", "gst", "income_tax", "tally", "system_inference", "ai_inference", "other") def get_profile(db: Session, client_id: int) -> ClientBusinessProfile | None: return db.execute(select(ClientBusinessProfile).where(ClientBusinessProfile.client_id == int(client_id))).scalar_one_or_none() def ensure_profile(db: Session, client: Client, actor_user_id: int | None = None) -> ClientBusinessProfile: row = get_profile(db, client.id) if row: return row row = ClientBusinessProfile( client_id=client.id, tenant_id=client.tenant_id, branch_id=client.branch_id, created_by_user_id=actor_user_id, updated_by_user_id=actor_user_id, ) db.add(row) db.flush() return row def list_sources(db: Session, client_id: int) -> list[ClientBusinessProfileSource]: return list(db.execute( select(ClientBusinessProfileSource) .where(ClientBusinessProfileSource.client_id == int(client_id)) .order_by(ClientBusinessProfileSource.created_at_utc.desc(), ClientBusinessProfileSource.id.desc()) ).scalars().all()) def _optional_bool(value: str | None) -> bool | None: if value in (None, "", "unknown"): return None return str(value).lower() in {"1", "true", "yes", "on"} def update_profile(db: Session, *, client: Client, actor_user_id: int, form) -> ClientBusinessProfile: row = ensure_profile(db, client, actor_user_id) row.primary_industry = (form.get("primary_industry") or "").strip() or None row.primary_business_activity = (form.get("primary_business_activity") or "").strip() or None row.secondary_business_activities = (form.get("secondary_business_activities") or "").strip() or None business_model = (form.get("business_model") or "").strip() row.business_model = business_model if business_model in BUSINESS_MODEL_OPTIONS else None row.main_products = (form.get("main_products") or "").strip() or None row.main_services = (form.get("main_services") or "").strip() or None row.inventory_maintained = _optional_bool(form.get("inventory_maintained")) row.project_job_based = _optional_bool(form.get("project_job_based")) row.capital_intensive = _optional_bool(form.get("capital_intensive")) row.vehicle_intensive = _optional_bool(form.get("vehicle_intensive")) row.notes = (form.get("notes") or "").strip() or None row.updated_by_user_id = actor_user_id # Editing a confirmed profile returns it to review so automated enrichment can # never silently remain marked confirmed after a human changes the facts. if row.profile_status == "confirmed": row.profile_status = "draft" row.confirmed_by_user_id = None row.confirmed_at_utc = None db.commit() db.refresh(row) return row def confirm_profile(db: Session, *, client: Client, actor_user_id: int) -> ClientBusinessProfile: row = ensure_profile(db, client, actor_user_id) row.profile_status = "confirmed" row.confidence_score = 100 row.confirmed_by_user_id = actor_user_id row.confirmed_at_utc = datetime.now(timezone.utc) row.updated_by_user_id = actor_user_id db.commit() db.refresh(row) return row def add_source(db: Session, *, client: Client, actor_user_id: int, form) -> ClientBusinessProfileSource: profile = ensure_profile(db, client, actor_user_id) source_type = (form.get("source_type") or "manual_user").strip() if source_type not in SOURCE_TYPE_OPTIONS: source_type = "other" raw_confidence = (form.get("source_confidence") or "50").strip() try: confidence = max(0, min(100, int(raw_confidence))) except ValueError: confidence = 50 observed_raw = (form.get("observed_on") or "").strip() observed_on = None if observed_raw: try: observed_on = date.fromisoformat(observed_raw) except ValueError: observed_on = None row = ClientBusinessProfileSource( profile_id=profile.id, client_id=client.id, tenant_id=client.tenant_id, source_type=source_type, source_reference=(form.get("source_reference") or "").strip() or None, observed_on=observed_on, confidence_score=confidence, is_authoritative=str(form.get("is_authoritative") or "").lower() in {"1", "true", "yes", "on"}, evidence_json={ "primary_industry": profile.primary_industry, "primary_business_activity": profile.primary_business_activity, "business_model": profile.business_model, "main_products": profile.main_products, "main_services": profile.main_services, }, notes=(form.get("source_notes") or "").strip() or None, created_by_user_id=actor_user_id, ) db.add(row) # Confidence is an evidence summary only. User confirmation remains authoritative. if profile.profile_status != "confirmed": scores = [s.confidence_score for s in list_sources(db, client.id)] + [confidence] profile.confidence_score = round(sum(scores) / len(scores)) if scores else 0 db.commit() db.refresh(row) return row