245 lines
5.8 KiB
Python
245 lines
5.8 KiB
Python
"""
|
|
Pydantic models for Odoo Directory data structures.
|
|
"""
|
|
|
|
from datetime import datetime
|
|
from enum import Enum
|
|
from typing import Optional, List, Dict, Any
|
|
from uuid import UUID
|
|
|
|
from pydantic import BaseModel, Field, HttpUrl
|
|
|
|
|
|
class TagCategory(str, Enum):
|
|
"""Tag category types."""
|
|
SERVICE = "service"
|
|
INDUSTRY = "industry"
|
|
MODULE = "module"
|
|
PARTNER_LEVEL = "partner_level"
|
|
|
|
|
|
class StepStatus(str, Enum):
|
|
"""Execution step status."""
|
|
PENDING = "pending"
|
|
RUNNING = "running"
|
|
COMPLETED = "completed"
|
|
FAILED = "failed"
|
|
SKIPPED = "skipped"
|
|
|
|
|
|
class Region(BaseModel):
|
|
"""Geographic region model."""
|
|
id: Optional[UUID] = None
|
|
name: str
|
|
slug: str
|
|
created_at: Optional[datetime] = None
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class Country(BaseModel):
|
|
"""Country model."""
|
|
id: Optional[UUID] = None
|
|
region_id: Optional[UUID] = None
|
|
name: str
|
|
name_local: Optional[str] = None
|
|
code: str # ISO 3166-1 alpha-2
|
|
slug: str
|
|
language: str # Primary language code
|
|
created_at: Optional[datetime] = None
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class City(BaseModel):
|
|
"""City model."""
|
|
id: Optional[UUID] = None
|
|
country_id: Optional[UUID] = None
|
|
name: str
|
|
name_local: Optional[str] = None
|
|
slug: str
|
|
state: Optional[str] = None
|
|
population: Optional[int] = None
|
|
latitude: Optional[float] = None
|
|
longitude: Optional[float] = None
|
|
created_at: Optional[datetime] = None
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class Tag(BaseModel):
|
|
"""Tag model for services, industries, modules, partner levels."""
|
|
id: Optional[UUID] = None
|
|
category: TagCategory
|
|
name: str
|
|
slug: str
|
|
description: Optional[str] = None
|
|
created_at: Optional[datetime] = None
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class Company(BaseModel):
|
|
"""Odoo partner company model."""
|
|
id: Optional[UUID] = None
|
|
city_id: Optional[UUID] = None
|
|
|
|
# Basic info (from Google Maps)
|
|
name: str
|
|
slug: str
|
|
google_place_id: Optional[str] = None
|
|
address: Optional[str] = None
|
|
phone: Optional[str] = None
|
|
website: Optional[str] = None
|
|
email: Optional[str] = None
|
|
|
|
# Google Maps data
|
|
rating: Optional[float] = None
|
|
review_count: Optional[int] = None
|
|
latitude: Optional[float] = None
|
|
longitude: Optional[float] = None
|
|
|
|
# Enriched data (from Firecrawl)
|
|
website_markdown: Optional[str] = None
|
|
logo_url: Optional[str] = None
|
|
|
|
# Generated content (from Haiku)
|
|
description: Optional[str] = None
|
|
content_html: Optional[str] = None
|
|
meta_title: Optional[str] = None
|
|
meta_description: Optional[str] = None
|
|
|
|
# Status tracking
|
|
status: str = "scraped" # scraped, enriched, content_generated, exported
|
|
last_scraped_at: Optional[datetime] = None
|
|
last_enriched_at: Optional[datetime] = None
|
|
last_content_at: Optional[datetime] = None
|
|
|
|
created_at: Optional[datetime] = None
|
|
updated_at: Optional[datetime] = None
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class CompanyTag(BaseModel):
|
|
"""Junction table for company-tag relationship."""
|
|
company_id: UUID
|
|
tag_id: UUID
|
|
confidence: Optional[float] = None # AI confidence score
|
|
created_at: Optional[datetime] = None
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class CityTag(BaseModel):
|
|
"""Aggregated tag counts per city."""
|
|
city_id: UUID
|
|
tag_id: UUID
|
|
company_count: int = 0
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class CountryTag(BaseModel):
|
|
"""Aggregated tag counts per country."""
|
|
country_id: UUID
|
|
tag_id: UUID
|
|
company_count: int = 0
|
|
city_count: int = 0
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class PlanStep(BaseModel):
|
|
"""Individual step in an execution plan."""
|
|
id: Optional[UUID] = None
|
|
plan_id: Optional[UUID] = None
|
|
step_name: str # scrape, enrich, content, export
|
|
city_id: Optional[UUID] = None
|
|
status: StepStatus = StepStatus.PENDING
|
|
started_at: Optional[datetime] = None
|
|
completed_at: Optional[datetime] = None
|
|
error_message: Optional[str] = None
|
|
items_processed: int = 0
|
|
items_total: int = 0
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class ExecutionPlan(BaseModel):
|
|
"""Execution plan for processing a country/region."""
|
|
id: Optional[UUID] = None
|
|
name: str
|
|
country_code: str
|
|
cities: List[str] # List of city slugs
|
|
status: StepStatus = StepStatus.PENDING
|
|
created_at: Optional[datetime] = None
|
|
started_at: Optional[datetime] = None
|
|
completed_at: Optional[datetime] = None
|
|
|
|
# Related steps
|
|
steps: List[PlanStep] = []
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
# API Response models
|
|
|
|
class ApifyGoogleMapsResult(BaseModel):
|
|
"""Result from Apify Google Maps scraper."""
|
|
title: str
|
|
address: Optional[str] = None
|
|
phone: Optional[str] = None
|
|
website: Optional[str] = None
|
|
rating: Optional[float] = None
|
|
reviewsCount: Optional[int] = None
|
|
placeId: Optional[str] = None
|
|
url: Optional[str] = None
|
|
location: Optional[Dict[str, float]] = None
|
|
|
|
class Config:
|
|
extra = "ignore"
|
|
|
|
|
|
class FirecrawlResult(BaseModel):
|
|
"""Result from Firecrawl website scraper."""
|
|
markdown: Optional[str] = None
|
|
metadata: Optional[Dict[str, Any]] = None
|
|
images: Optional[List[str]] = None
|
|
|
|
class Config:
|
|
extra = "ignore"
|
|
|
|
|
|
class HaikuTagsResponse(BaseModel):
|
|
"""Response from Haiku tag extraction."""
|
|
services: List[str] = []
|
|
industries: List[str] = []
|
|
modules: List[str] = []
|
|
partner_level: Optional[str] = None
|
|
confidence: float = 0.0
|
|
|
|
class Config:
|
|
extra = "ignore"
|
|
|
|
|
|
class HaikuContentResponse(BaseModel):
|
|
"""Response from Haiku content generation."""
|
|
description: str
|
|
content_html: str
|
|
meta_title: str
|
|
meta_description: str
|
|
|
|
class Config:
|
|
extra = "ignore"
|