"""Reference style service — builds and caches the KB reference style profile. The reference profile is extracted from Behrang's indexed reports (``__rics_kb__`` tenant) and stored in the style cache under the same tenant ID. Used as a fallback in ``_get_or_build_style_profile`` when a user has not yet uploaded any documents of their own. """ from __future__ import annotations import logging from app.async_executor import run_sync_in_executor from app.cache import style_cache from app.config import settings from app.generator.style_analyzer import build_reference_style_profile from app.retrieval.retriever import retrieve_async logger = logging.getLogger(__name__) # Diverse queries to sample a broad cross-section of survey writing from the KB _REFERENCE_QUERIES = [ "building condition survey inspection findings", "roof structure tiles condition recommendation", "main walls external condition damp penetration", "services electrical gas plumbing drainage condition", "property description construction type age", "chimney stack flashing pointing condition", "floors structure condition movement settlement", "windows doors frames condition repair", ] async def seed_reference_style_profile() -> None: """Build and cache the reference style profile from the KB tenant. 1. Checks the style cache first — skips if already cached. 2. Retrieves representative chunks from the KB vectorstore using multiple RICS-relevant queries. 3. Calls ``build_reference_style_profile`` (richer analyser with example paragraph extraction). 4. Stores the result under the KB tenant key in the style cache. Safe to call on every startup — the cache check makes it a no-op once seeded. """ if not settings.knowledge_base_enabled: return tenant = settings.knowledge_base_tenant_id if style_cache.get(tenant) is not None: logger.debug("Reference style cache hit for KB tenant — skipping seed") return if not settings.openai_api_key: logger.info("No OpenAI key — reference style profile will use mock fallback") return logger.info("Building reference style profile from KB tenant '%s'", tenant) all_texts: list[str] = [] seen: set[str] = set() async def _collect() -> None: for query in _REFERENCE_QUERIES: try: results = await retrieve_async(query=query, tenant_id=tenant, k=6) for r in results: text = r.text.strip() if text and text not in seen: seen.add(text) all_texts.append(text) except Exception as exc: logger.debug("KB retrieve failed for query '%s': %s", query, exc) await _collect() if not all_texts: logger.warning( "No chunks found in KB tenant '%s' — reference style not seeded. " "Upload reference documents or check knowledge_base_dirs config.", tenant, ) return logger.info("Analysing reference style from %d unique KB chunks", len(all_texts)) profile = await run_sync_in_executor( build_reference_style_profile, sample_texts=all_texts, openai_api_key=settings.openai_api_key, chat_model=settings.chat_model, ) style_cache.set(tenant, profile) logger.info( "Reference style cached: tone=%s formality=%s example_paragraphs=%d", profile.tone, profile.formality_level, len(profile.example_paragraphs), )