Coverage for src/local_deep_research/domain_classifier/models.py: 100%

18 statements  

« prev     ^ index     » next       coverage.py v7.15.1, created at 2026-07-19 23:35 +0000

1"""Database models for domain classification.""" 

2 

3from sqlalchemy import Column, String, Text, Float, Integer, Index 

4from sqlalchemy_utc import UtcDateTime, utcnow 

5 

6# Import Base from the dependency-free database.base leaf, NOT the 

7# ..database.models package. database.models eagerly imports every model — 

8# including this one — so importing Base through it would re-enter a 

9# partially-initialized database.models and raise "cannot import name 

10# 'DomainClassification'". database.base holds the same Base object, so metadata 

11# registration is unchanged. Regression: tests/test_domain_classifier_no_import_cycle.py 

12from ..database.base import Base 

13 

14 

15class DomainClassification(Base): 

16 """Store domain classifications generated by LLM.""" 

17 

18 __tablename__ = "domain_classifications" 

19 

20 id = Column(Integer, primary_key=True, autoincrement=True) 

21 domain = Column(String(255), unique=True, nullable=False, index=True) 

22 category = Column(String(100), nullable=False) 

23 subcategory = Column(String(100)) 

24 confidence = Column(Float, default=0.0) 

25 reasoning = Column(Text) # Store LLM's reasoning for the classification 

26 sample_titles = Column(Text) # JSON array of sample titles from this domain 

27 sample_count = Column( 

28 Integer, default=0 

29 ) # Number of resources used for classification 

30 created_at = Column(UtcDateTime, default=utcnow()) 

31 updated_at = Column(UtcDateTime, default=utcnow(), onupdate=utcnow()) 

32 

33 # Create index for faster lookups 

34 __table_args__ = (Index("idx_domain_category", "domain", "category"),) 

35 

36 def to_dict(self): 

37 """Convert to dictionary for JSON serialization.""" 

38 return { 

39 "id": self.id, 

40 "domain": self.domain, 

41 "category": self.category, 

42 "subcategory": self.subcategory, 

43 "confidence": self.confidence, 

44 "reasoning": self.reasoning, 

45 "sample_titles": self.sample_titles, 

46 "sample_count": self.sample_count, 

47 "created_at": self.created_at.isoformat() 

48 if self.created_at 

49 else None, 

50 "updated_at": self.updated_at.isoformat() 

51 if self.updated_at 

52 else None, 

53 }