Coverage for src / idx_api / models / brokerage_vision_settings.py: 56%

32 statements  

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

1"""Brokerage Vision Settings - per-brokerage AI vision configuration.""" 

2 

3from typing import TYPE_CHECKING, Optional 

4 

5from sqlalchemy import Boolean, ForeignKey, Integer, String, Text 

6from sqlalchemy.orm import Mapped, mapped_column, relationship 

7 

8from idx_api.models.base import Base, TimestampMixin 

9 

10if TYPE_CHECKING: 

11 from idx_api.models.brokerage import Brokerage 

12 

13 

14class BrokerageVisionSettings(Base, TimestampMixin): 

15 """Per-brokerage customization for AI vision analysis. 

16 

17 Allows brokerages to customize how property photos are analyzed, 

18 what vocabulary to use, and how descriptions are generated. 

19 

20 Example use cases: 

21 - Mountain region brokerage: emphasize "views", "ski access", "wildlife" 

22 - Luxury brokerage: use upscale language, focus on "gourmet kitchen", "spa bath" 

23 - Beach/coastal: add "ocean views", "lanai", "hurricane shutters" 

24 - Farm/ranch: emphasize "acreage", "barn", "pasture", "water rights" 

25 """ 

26 

27 __tablename__ = "brokerage_vision_settings" 

28 

29 id: Mapped[int] = mapped_column(primary_key=True) 

30 brokerage_id: Mapped[int] = mapped_column( 

31 Integer, ForeignKey("brokerages.id", ondelete="CASCADE"), unique=True, nullable=False 

32 ) 

33 

34 # Feature toggle 

35 vision_enabled: Mapped[bool] = mapped_column(Boolean, default=True, server_default="1") 

36 

37 # Prompt customization 

38 # Additional instructions appended to the base prompt 

39 additional_instructions: Mapped[Optional[str]] = mapped_column( 

40 Text, 

41 comment="Extra instructions for the vision model, e.g., 'Focus on luxury features and use upscale language'" 

42 ) 

43 

44 # Custom vocabulary additions (appended to system default) 

45 # Format: "- Category: term1, term2, term3\n- Category2: term4, term5" 

46 custom_vocabulary: Mapped[Optional[str]] = mapped_column( 

47 Text, 

48 comment="Additional feature vocabulary specific to this region/market" 

49 ) 

50 

51 # Priority features to always highlight if visible 

52 # Comma-separated list, e.g., "mountain views,ski access,horse property" 

53 priority_features: Mapped[Optional[str]] = mapped_column( 

54 String(500), 

55 comment="Comma-separated features to emphasize if visible" 

56 ) 

57 

58 # Tone/style guidance 

59 # Options: "professional", "luxury", "friendly", "technical" 

60 description_tone: Mapped[Optional[str]] = mapped_column( 

61 String(50), 

62 default="professional", 

63 comment="Tone for generated descriptions" 

64 ) 

65 

66 # Room type vocabulary overrides (JSON string) 

67 # e.g., {"lanai": "covered outdoor living", "bonus_room": "flex space"} 

68 room_type_aliases: Mapped[Optional[str]] = mapped_column( 

69 Text, 

70 comment="JSON mapping of regional room names to standard terms" 

71 ) 

72 

73 # Max photos to analyze per property (override system default) 

74 max_photos_per_property: Mapped[Optional[int]] = mapped_column( 

75 Integer, 

76 comment="Override system default for max photos per property" 

77 ) 

78 

79 # Relationship 

80 brokerage: Mapped["Brokerage"] = relationship(back_populates="vision_settings") 

81 

82 def __repr__(self) -> str: 

83 return f"<BrokerageVisionSettings(brokerage_id={self.brokerage_id}, tone='{self.description_tone}')>" 

84 

85 def get_full_vocabulary(self, system_default: str) -> str: 

86 """Merge system vocabulary with brokerage custom vocabulary.""" 

87 if not self.custom_vocabulary: 

88 return system_default 

89 return f"{system_default}\n{self.custom_vocabulary}" 

90 

91 def get_prompt_additions(self) -> str: 

92 """Build additional prompt instructions from settings.""" 

93 parts = [] 

94 

95 if self.additional_instructions: 

96 parts.append(self.additional_instructions) 

97 

98 if self.priority_features: 

99 features = [f.strip() for f in self.priority_features.split(",")] 

100 parts.append(f"Pay special attention to these features if visible: {', '.join(features)}") 

101 

102 if self.description_tone and self.description_tone != "professional": 

103 tone_guidance = { 

104 "luxury": "Use upscale, sophisticated language appropriate for luxury properties.", 

105 "friendly": "Use warm, welcoming language that appeals to families.", 

106 "technical": "Include precise technical details about materials and construction.", 

107 } 

108 if self.description_tone in tone_guidance: 

109 parts.append(tone_guidance[self.description_tone]) 

110 

111 return "\n".join(parts) if parts else ""