Coverage for src / idx_api / config.py: 100%

38 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2025-12-28 11:09 -0700

1"""API configuration.""" 

2 

3from pathlib import Path 

4 

5from pydantic import Field 

6from pydantic_settings import BaseSettings, SettingsConfigDict 

7 

8 

9class Settings(BaseSettings): 

10 """API settings from environment.""" 

11 

12 model_config = SettingsConfigDict( 

13 env_file=".env", 

14 extra="ignore", 

15 ) 

16 

17 # Database (PostgreSQL primary, SQLite for legacy/sync) 

18 database_url: str = Field( 

19 default="postgresql://mlsgrid:mlsgrid123@localhost:5432/mlsgrid", 

20 description="PostgreSQL connection URL" 

21 ) 

22 # Legacy SQLite paths (for sync service and migration) 

23 sqlite_db_path: Path = Field(default=Path("/data/mlsgrid.db")) 

24 sync_state_db_path: Path = Field(default=Path("/data/sync_state.db")) 

25 

26 # Ollama 

27 ollama_base_url: str = Field(default="https://ollama.supported.systems") 

28 ollama_embed_model: str = Field(default="mxbai-embed-large") 

29 embedding_dimensions: int = Field(default=1024) # mxbai-embed-large dimensions 

30 

31 # Vision Model (for property photo analysis) 

32 vision_model: str = Field(default="qwen3-vl:latest") 

33 vision_enabled: bool = Field(default=True) 

34 vision_max_photos_per_property: int = Field(default=5) 

35 vision_image_max_size: int = Field(default=1024, description="Max image dimension for resizing") 

36 vision_request_timeout: float = Field(default=120.0, description="Vision API timeout in seconds") 

37 vision_max_retries: int = Field(default=2, description="Max retries for failed vision requests") 

38 vision_concurrency: int = Field(default=3, description="Max concurrent vision requests") 

39 

40 # Vision Prompt Templates (system-wide defaults, can be overridden per-brokerage) 

41 # These use {placeholder} syntax for brokerage customization 

42 vision_prompt_base: str = Field( 

43 default="""Analyze this real estate photo and extract structured tags. 

44 

45Return a JSON object with these fields: 

46{{ 

47 "room_type": "kitchen|bathroom|bedroom|living_room|dining|exterior|backyard|garage|basement|office|other", 

48 "features": ["list", "of", "visible", "features"], 

49 "materials": ["granite", "hardwood", "tile", "etc"], 

50 "style": "modern|traditional|farmhouse|craftsman|contemporary|mid_century|rustic", 

51 "condition": "new|updated|original|dated", 

52 "highlights": ["buyer-exciting", "features"], 

53 "quality_score": 1-5 

54}} 

55 

56{additional_instructions} 

57 

58For features, use searchable terms buyers look for: 

59{feature_vocabulary} 

60 

61Only include features clearly visible in the photo. Return valid JSON only.""", 

62 description="Base vision prompt template with placeholders" 

63 ) 

64 

65 vision_default_vocabulary: str = Field( 

66 default="""- Kitchen: island, pantry, breakfast bar, double oven, gas range, granite countertops 

67- Bathroom: soaking tub, walk-in shower, double vanity, jetted tub, tile work 

68- Living: fireplace, built-ins, vaulted ceiling, open floor plan, hardwood floors 

69- Exterior: pool, covered patio, deck, mountain views, workshop, RV parking 

70- Garage: shop, workbench, storage, oversized, 3-car""", 

71 description="Default feature vocabulary for vision prompts" 

72 ) 

73 

74 # SigLIP Visual Embeddings (Approach B) 

75 siglip_enabled: bool = Field(default=True) 

76 siglip_base_url: str = Field(default="https://siglip.supported.systems") 

77 siglip_dimensions: int = Field(default=1024) # SigLIP-Large-patch16-384 dimensions 

78 

79 # Multi-modal search fusion weights (should sum to 1.0) 

80 # Tuned based on A/B comparison: text dominates, descriptions add value, visual minimal 

81 search_weight_text: float = Field(default=0.50) 

82 search_weight_descriptions: float = Field(default=0.45) 

83 search_weight_visual: float = Field(default=0.05) 

84 

85 # API 

86 api_host: str = Field(default="0.0.0.0") 

87 api_port: int = Field(default=8000) 

88 environment: str = Field(default="dev") 

89 

90 # CORS - allow frontend origins 

91 cors_origins: list[str] = Field( 

92 default=[ 

93 "http://localhost:4321", 

94 "http://localhost:4322", 

95 "http://localhost:3000", 

96 "https://mlsgrid.l.supported.systems", 

97 "https://ei.supported.systems", 

98 "http://idx-web:4321", # Docker internal 

99 ] 

100 ) 

101 

102 # Keycloak Authentication 

103 keycloak_hostname: str = Field(default="keycloak.l.supported.systems") 

104 keycloak_realm: str = Field(default="mlsgrid-idx") 

105 keycloak_client_id: str = Field(default="idx-web") 

106 keycloak_client_secret: str = Field(default="") 

107 

108 # JWT Configuration 

109 jwt_algorithm: str = Field(default="RS256") 

110 jwt_public_key: str = Field(default="") 

111 

112 # Domain Verification 

113 domain_cname_target: str = Field( 

114 default="idx.l.supported.systems", 

115 description="Expected CNAME target for domain verification" 

116 ) 

117 

118 

119settings = Settings()