# coding: utf-8

"""
    Mixpeek API

    This is the Mixpeek API, providing access to various endpoints for data processing and retrieval.

    The version of the OpenAPI document: 1.3.0
    Contact: info@mixpeek.com
    Generated by OpenAPI Generator (https://openapi-generator.tech)

    Do not edit the class manually.
"""  # noqa: E501


from __future__ import annotations
import pprint
import re  # noqa: F401
import json

from pydantic import BaseModel, ConfigDict, Field, StrictFloat, StrictInt, StrictStr, field_validator
from typing import Any, ClassVar, Dict, List, Optional, Union
from typing_extensions import Annotated
from mixpeek.models.diversity_feature_config import DiversityFeatureConfig
from typing import Optional, Set
from typing_extensions import Self

class StageParamsMmr(BaseModel):
    """
    Configuration for MMR (Maximal Marginal Relevance) result diversification.  **Stage Category**: SORT  **Transformation**: N documents → N documents (reordered for diversity)  **Purpose**: Reorders search results to balance relevance with diversity, preventing result sets dominated by near-duplicate or highly similar content. Particularly valuable for multimodal search where visual/semantic similarity can lead to repetitive results.  **When to Use**:     - After feature_search when results may contain near-duplicates     - When users expect variety in search results     - For recommendation systems needing diverse suggestions     - When different facets of a query should be represented  **When NOT to Use**:     - When exact relevance ranking is critical (use sort_relevance)     - For small result sets (<5 documents) where diversity matters less     - When duplicates have already been removed via group_by  **Three Diversity Modes** (mutually exclusive):  | Mode | Config Field | Description | |------|--------------|-------------| | Single Feature | `diversity_feature_uri` | Diversity in one embedding space | | Multi-Feature | `diversity_features` | Weighted fusion across spaces | | Attribute-Based | `diversity_fields` | Diversify by metadata values |  **Mode Selection Logic**:     1. If `diversity_feature_uri` is set → Single Feature mode     2. If `diversity_features` is set → Multi-Feature mode     3. If `diversity_fields` is set → Attribute-Based mode     4. If none set → Auto-detect from previous feature_search stage  **Common Pipeline Position**: feature_search → mmr → (optional rerank)  Examples:     Single feature diversity (simplest):         ```json         {             \"lambda_\": 0.7,             \"top_k\": 25,             \"diversity_feature_uri\": \"mixpeek://clip@v1/image_embedding\"         }         ```      Multi-feature diversity (multimodal):         ```json         {             \"lambda_\": 0.6,             \"top_k\": 20,             \"diversity_features\": [                 {\"feature_uri\": \"mixpeek://text@v1/embedding\", \"weight\": 0.5},                 {\"feature_uri\": \"mixpeek://clip@v1/embedding\", \"weight\": 0.5}             ]         }         ```      Attribute-based diversity (no embeddings):         ```json         {             \"lambda_\": 0.5,             \"top_k\": 30,             \"diversity_fields\": [\"metadata.category\", \"metadata.source\"]         }         ```
    """ # noqa: E501
    var_lambda: Optional[Union[Annotated[float, Field(le=1.0, strict=True, ge=0.0)], Annotated[int, Field(le=1, strict=True, ge=0)]]] = Field(default=0.7, description="OPTIONAL. Balance between relevance and diversity (default: 0.7). Higher values favor relevance, lower values favor diversity.   Guidelines: - 1.0: Pure relevance (no diversity, equivalent to no MMR) - 0.7-0.8: Slight diversity while maintaining relevance (recommended) - 0.5: Balanced relevance and diversity - 0.3-0.4: High diversity, may sacrifice some relevance - 0.0: Maximum diversity (ignores relevance scores)", alias="lambda")
    top_k: Optional[Annotated[int, Field(le=500, strict=True, ge=1)]] = Field(default=25, description="OPTIONAL. Number of documents to return after MMR reordering (default: 25). MMR processes all input documents but returns only top_k. Set to match your UI's result count for optimal diversity.")
    diversity_feature_uri: Optional[StrictStr] = Field(default='null', description="OPTIONAL. Single embedding feature URI for diversity computation. Use this for simple cases where one embedding space captures similarity.   If not specified and no other mode is set, auto-detects from the previous feature_search stage's feature_uri.   Format: 'mixpeek://extractor@version/output'")
    diversity_features: Optional[List[DiversityFeatureConfig]] = Field(default=None, description="OPTIONAL. Multiple embedding features for weighted diversity fusion. Use this for multimodal content where similarity should consider multiple embedding spaces (e.g., text + image + audio).   Diversity scores from each feature are combined using weights. Weights are normalized to sum to 1.0.")
    diversity_fields: Optional[List[StrictStr]] = Field(default=None, description="OPTIONAL. Metadata fields to use for attribute-based diversity. No embeddings required - uses field value matching.   Documents with the same field values are considered similar. Useful for categorical diversity (one per category, source, type).   Dot notation supported for nested fields.")
    diversity_field_weights: Optional[Dict[str, Union[StrictFloat, StrictInt]]] = Field(default=None, description="OPTIONAL. Weights for each diversity field (for attribute-based mode). If not specified, all fields are weighted equally. Keys must match field paths in diversity_fields.")
    score_field: Optional[StrictStr] = Field(default='score', description="OPTIONAL. Document field containing relevance score from previous stage. Used as the 'relevance' component in MMR formula.")
    mmr_score_field: Optional[StrictStr] = Field(default='scores.mmr', description="OPTIONAL. Field path to store the computed MMR score. Useful for debugging and understanding ranking decisions.")
    similarity_metric: Optional[StrictStr] = Field(default='cosine', description="OPTIONAL. Similarity metric for embedding comparison. Cosine is recommended for normalized embeddings (most common).")
    __properties: ClassVar[List[str]] = ["lambda", "top_k", "diversity_feature_uri", "diversity_features", "diversity_fields", "diversity_field_weights", "score_field", "mmr_score_field", "similarity_metric"]

    @field_validator('similarity_metric')
    def similarity_metric_validate_enum(cls, value):
        """Validates the enum"""
        if value is None:
            return value

        if value not in set(['cosine', 'dot', 'euclidean']):
            raise ValueError("must be one of enum values ('cosine', 'dot', 'euclidean')")
        return value

    model_config = ConfigDict(
        populate_by_name=True,
        validate_assignment=True,
        protected_namespaces=(),
    )


    def to_str(self) -> str:
        """Returns the string representation of the model using alias"""
        return pprint.pformat(self.model_dump(by_alias=True))

    def to_json(self) -> str:
        """Returns the JSON representation of the model using alias"""
        # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
        return json.dumps(self.to_dict())

    @classmethod
    def from_json(cls, json_str: str) -> Optional[Self]:
        """Create an instance of StageParamsMmr from a JSON string"""
        return cls.from_dict(json.loads(json_str))

    def to_dict(self) -> Dict[str, Any]:
        """Return the dictionary representation of the model using alias.

        This has the following differences from calling pydantic's
        `self.model_dump(by_alias=True)`:

        * `None` is only added to the output dict for nullable fields that
          were set at model initialization. Other fields with value `None`
          are ignored.
        """
        excluded_fields: Set[str] = set([
        ])

        _dict = self.model_dump(
            by_alias=True,
            exclude=excluded_fields,
            exclude_none=True,
        )
        # override the default output from pydantic by calling `to_dict()` of each item in diversity_features (list)
        _items = []
        if self.diversity_features:
            for _item_diversity_features in self.diversity_features:
                if _item_diversity_features:
                    _items.append(_item_diversity_features.to_dict())
            _dict['diversity_features'] = _items
        return _dict

    @classmethod
    def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
        """Create an instance of StageParamsMmr from a dict"""
        if obj is None:
            return None

        if not isinstance(obj, dict):
            return cls.model_validate(obj)

        _obj = cls.model_validate({
            "lambda": obj.get("lambda") if obj.get("lambda") is not None else 0.7,
            "top_k": obj.get("top_k") if obj.get("top_k") is not None else 25,
            "diversity_feature_uri": obj.get("diversity_feature_uri") if obj.get("diversity_feature_uri") is not None else 'null',
            "diversity_features": [DiversityFeatureConfig.from_dict(_item) for _item in obj["diversity_features"]] if obj.get("diversity_features") is not None else None,
            "diversity_fields": obj.get("diversity_fields"),
            "diversity_field_weights": obj.get("diversity_field_weights"),
            "score_field": obj.get("score_field") if obj.get("score_field") is not None else 'score',
            "mmr_score_field": obj.get("mmr_score_field") if obj.get("mmr_score_field") is not None else 'scores.mmr',
            "similarity_metric": obj.get("similarity_metric") if obj.get("similarity_metric") is not None else 'cosine'
        })
        return _obj


