from datetime import datetime from pydantic import Field from cbh.api.common.dto import TradeDirection from cbh.api.rules.dto import RuleAction, RuleScope from cbh.core.database import MongoBaseModel class RuleModel(MongoBaseModel): """A broker's own logic, written down once and replayed on every line. Matching is deterministic and narrow: every condition that is set must hold. A rule with no conditions at all is refused on the way in, because it would silently touch the whole pack. """ name: str description: str | None = None scope: RuleScope = RuleScope.CLIENT clientProfileId: str | None = None active: bool = True codePrefix: str | None = None originCountry: str | None = None destinationCountry: str | None = None direction: TradeDirection | None = None descriptionContains: str | None = None action: RuleAction = RuleAction.NOTE pinnedCode: str | None = None documentCodes: list[str] = Field(default_factory=list) note: str | None = None createdFromBatchId: str | None = None createdFromRowIndex: int | None = None createdBy: str | None = None hitCount: int = 0 datetimeLastHit: datetime | None = None datetimeInserted: datetime = Field(default_factory=datetime.now) datetimeUpdated: datetime = Field(default_factory=datetime.now) @property def conditions(self) -> dict[str, str]: """The conditions actually set, for display and for specificity.""" present = { "codePrefix": self.codePrefix, "originCountry": self.originCountry, "destinationCountry": self.destinationCountry, "direction": self.direction.name if self.direction else None, "descriptionContains": self.descriptionContains, } return {key: value for key, value in present.items() if value} @property def specificity(self) -> int: """How many conditions a rule sets. The narrowest rule wins a conflict.""" return len(self.conditions)