# 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 datetime import datetime
from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictFloat, StrictInt, StrictStr, field_validator
from typing import Any, ClassVar, Dict, List, Optional, Union
from mixpeek.models.llm_labeling_output import LLMLabelingOutput
from mixpeek.models.source_enrichment_config import SourceEnrichmentConfig
from mixpeek.models.task_status_enum import TaskStatusEnum
from typing import Optional, Set
from typing_extensions import Self

class ClusterMetadata(BaseModel):
    """
    Cluster job metadata stored in MongoDB clusters collection.  This is separate from cluster documents themselves. Tracks job-level configuration, status, and summary statistics.  Supports both vector and attribute clustering with appropriate metadata.
    """ # noqa: E501
    cluster_id: Optional[StrictStr] = Field(default=None, description="Unique cluster job identifier")
    cluster_name: StrictStr = Field(description="Human-readable cluster name")
    namespace_id: StrictStr = Field(description="Namespace this cluster belongs to")
    input_collections: List[StrictStr] = Field(description="Source collection IDs that were clustered")
    source_bucket_ids: Optional[List[StrictStr]] = Field(default=None, description="Source bucket IDs that the input collections originated from. Enables bucket lineage tracking.")
    filters: Optional[Dict[str, Any]] = Field(default=None, description="Optional filters that were applied to pre-filter documents before clustering")
    cluster_type: StrictStr = Field(description="Type of clustering: vector (embedding-based) or attribute (metadata-based)")
    feature_uris: Optional[List[StrictStr]] = Field(default=None, description="Feature URIs that were clustered (mixpeek://{extractor}@{version}/{output}). Only for vector clustering.")
    multi_feature_strategy: Optional[StrictStr] = Field(default=None, description="Strategy used if multiple features (concatenate/independent/weighted). Only for vector clustering.")
    learned_weights: Optional[Dict[str, Union[StrictFloat, StrictInt]]] = Field(default=None, description="Automatically learned feature weights (when multi_feature_strategy='weighted'). Keys are feature URIs, values are learned weights. Only populated after clustering execution completes.")
    learning_quality_score: Optional[Union[StrictFloat, StrictInt]] = Field(default=None, description="Clustering quality score from weight learning (e.g., silhouette score). Only populated when multi_feature_strategy='weighted' and weights were learned.")
    effective_feature_method: Optional[StrictStr] = Field(default=None, description="Method for calculating cluster centroids (mean/median/medoid). Only for vector clustering.")
    clustered_attributes: Optional[List[StrictStr]] = Field(default=None, description="Attribute field names that were clustered. Only for attribute clustering.")
    hierarchical_grouping: Optional[StrictBool] = Field(default=None, description="Whether hierarchical clustering was used. Only for attribute clustering.")
    aggregation_method: Optional[StrictStr] = Field(default=None, description="Method for aggregating attributes (most_frequent/first/last). Only for attribute clustering.")
    output_collection_ids: Optional[List[StrictStr]] = Field(default=None, description="Collection IDs where cluster documents are stored. For single output: list with one collection ID. For per-feature output: list with one collection ID per feature.")
    output_collection_names: Optional[List[StrictStr]] = Field(default=None, description="Names of output collections. Corresponds to output_collection_ids.")
    algorithm: Optional[StrictStr] = Field(default=None, description="Clustering algorithm used (hdbscan, kmeans, attribute_based, etc.)")
    algorithm_params: Optional[Dict[str, Any]] = Field(default=None, description="Algorithm-specific parameters (not used for attribute_based)")
    enrich_source: Optional[StrictBool] = Field(default=False, description="Whether source documents were enriched with cluster_id")
    source_enrichment_config: Optional[SourceEnrichmentConfig] = Field(default=None, description="Configuration for source enrichment (if enrich_source=True)")
    llm_labeling: Optional[LLMLabelingOutput] = Field(default=None, description="Configuration for LLM-based cluster labeling (applies to all cluster types)")
    num_clusters: Optional[StrictInt] = Field(default=None, description="Number of clusters found (excludes noise/outliers, populated after execution)")
    num_documents_clustered: Optional[StrictInt] = Field(default=None, description="Total documents processed")
    execution_time_seconds: Optional[Union[StrictFloat, StrictInt]] = Field(default=None, description="Time taken to complete clustering")
    hierarchy_detected: Optional[StrictBool] = Field(default=False, description="Whether implicit hierarchy was detected (multi-feature independent) or created (hierarchical attributes)")
    parent_cluster_id: Optional[StrictStr] = Field(default=None, description="For child clusters in hierarchy")
    child_cluster_ids: Optional[List[StrictStr]] = Field(default=None, description="For parent clusters")
    hierarchy_relationships: Optional[List[Dict[str, Any]]] = Field(default=None, description="Parent-child relationships detected from cluster membership overlap")
    status: Optional[TaskStatusEnum] = Field(default=None, description="Cluster job status (propagated from TaskService)")
    last_execution_task_id: Optional[StrictStr] = Field(default=None, description="Most recent task ID for this cluster")
    created_at: Optional[datetime] = Field(default=None, description="When cluster was created")
    updated_at: Optional[datetime] = Field(default=None, description="When cluster was last updated")
    last_executed_at: Optional[datetime] = Field(default=None, description="Last execution timestamp")
    completed_at: Optional[datetime] = Field(default=None, description="When clustering completed successfully")
    llm_labeling_errors: Optional[List[StrictStr]] = Field(default=None, description="List of errors encountered during LLM labeling (if any). Stored in MongoDB cluster metadata only, NOT in Qdrant cluster documents. Used to track LLM failures while allowing fallback labels to work.")
    metadata: Optional[Dict[str, Any]] = Field(default=None, description="Additional user-defined metadata")
    __properties: ClassVar[List[str]] = ["cluster_id", "cluster_name", "namespace_id", "input_collections", "source_bucket_ids", "filters", "cluster_type", "feature_uris", "multi_feature_strategy", "learned_weights", "learning_quality_score", "effective_feature_method", "clustered_attributes", "hierarchical_grouping", "aggregation_method", "output_collection_ids", "output_collection_names", "algorithm", "algorithm_params", "enrich_source", "source_enrichment_config", "llm_labeling", "num_clusters", "num_documents_clustered", "execution_time_seconds", "hierarchy_detected", "parent_cluster_id", "child_cluster_ids", "hierarchy_relationships", "status", "last_execution_task_id", "created_at", "updated_at", "last_executed_at", "completed_at", "llm_labeling_errors", "metadata"]

    @field_validator('cluster_type')
    def cluster_type_validate_enum(cls, value):
        """Validates the enum"""
        if value not in set(['vector', 'attribute']):
            raise ValueError("must be one of enum values ('vector', 'attribute')")
        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 ClusterMetadata 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 source_enrichment_config
        if self.source_enrichment_config:
            _dict['source_enrichment_config'] = self.source_enrichment_config.to_dict()
        # override the default output from pydantic by calling `to_dict()` of llm_labeling
        if self.llm_labeling:
            _dict['llm_labeling'] = self.llm_labeling.to_dict()
        return _dict

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

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

        _obj = cls.model_validate({
            "cluster_id": obj.get("cluster_id"),
            "cluster_name": obj.get("cluster_name"),
            "namespace_id": obj.get("namespace_id"),
            "input_collections": obj.get("input_collections"),
            "source_bucket_ids": obj.get("source_bucket_ids"),
            "filters": obj.get("filters"),
            "cluster_type": obj.get("cluster_type"),
            "feature_uris": obj.get("feature_uris"),
            "multi_feature_strategy": obj.get("multi_feature_strategy"),
            "learned_weights": obj.get("learned_weights"),
            "learning_quality_score": obj.get("learning_quality_score"),
            "effective_feature_method": obj.get("effective_feature_method"),
            "clustered_attributes": obj.get("clustered_attributes"),
            "hierarchical_grouping": obj.get("hierarchical_grouping"),
            "aggregation_method": obj.get("aggregation_method"),
            "output_collection_ids": obj.get("output_collection_ids"),
            "output_collection_names": obj.get("output_collection_names"),
            "algorithm": obj.get("algorithm"),
            "algorithm_params": obj.get("algorithm_params"),
            "enrich_source": obj.get("enrich_source") if obj.get("enrich_source") is not None else False,
            "source_enrichment_config": SourceEnrichmentConfig.from_dict(obj["source_enrichment_config"]) if obj.get("source_enrichment_config") is not None else None,
            "llm_labeling": LLMLabelingOutput.from_dict(obj["llm_labeling"]) if obj.get("llm_labeling") is not None else None,
            "num_clusters": obj.get("num_clusters"),
            "num_documents_clustered": obj.get("num_documents_clustered"),
            "execution_time_seconds": obj.get("execution_time_seconds"),
            "hierarchy_detected": obj.get("hierarchy_detected") if obj.get("hierarchy_detected") is not None else False,
            "parent_cluster_id": obj.get("parent_cluster_id"),
            "child_cluster_ids": obj.get("child_cluster_ids"),
            "hierarchy_relationships": obj.get("hierarchy_relationships"),
            "status": obj.get("status"),
            "last_execution_task_id": obj.get("last_execution_task_id"),
            "created_at": obj.get("created_at"),
            "updated_at": obj.get("updated_at"),
            "last_executed_at": obj.get("last_executed_at"),
            "completed_at": obj.get("completed_at"),
            "llm_labeling_errors": obj.get("llm_labeling_errors"),
            "metadata": obj.get("metadata")
        })
        return _obj


