diff --git a/src/main/java/cloud/eppo/api/AllocationDetails.java b/src/main/java/cloud/eppo/api/AllocationDetails.java
new file mode 100644
index 0000000..b7bb8b9
--- /dev/null
+++ b/src/main/java/cloud/eppo/api/AllocationDetails.java
@@ -0,0 +1,30 @@
+package cloud.eppo.api;
+
+/**
+ * Details about an allocation evaluation, including its key, evaluation status, and position in the
+ * allocation list.
+ */
+public class AllocationDetails {
+ private final String key;
+ private final AllocationEvaluationCode allocationEvaluationCode;
+ private final int orderPosition;
+
+ public AllocationDetails(
+ String key, AllocationEvaluationCode allocationEvaluationCode, int orderPosition) {
+ this.key = key;
+ this.allocationEvaluationCode = allocationEvaluationCode;
+ this.orderPosition = orderPosition;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public AllocationEvaluationCode getAllocationEvaluationCode() {
+ return allocationEvaluationCode;
+ }
+
+ public int getOrderPosition() {
+ return orderPosition;
+ }
+}
diff --git a/src/main/java/cloud/eppo/api/AllocationEvaluationCode.java b/src/main/java/cloud/eppo/api/AllocationEvaluationCode.java
new file mode 100644
index 0000000..1888019
--- /dev/null
+++ b/src/main/java/cloud/eppo/api/AllocationEvaluationCode.java
@@ -0,0 +1,61 @@
+package cloud.eppo.api;
+
+/**
+ * Enum representing the result code of an allocation evaluation within a flag.
+ *
+ *
Allocations are evaluated in order, and this code indicates why an allocation was or was not
+ * selected.
+ */
+public enum AllocationEvaluationCode {
+ /** Allocation rules matched and the allocation was selected. */
+ MATCH("MATCH"),
+
+ /** Allocation rules did not match the subject attributes. */
+ FAILING_RULE("FAILING_RULE"),
+
+ /** Current time is before the allocation's start time. */
+ BEFORE_START_TIME("BEFORE_START_TIME"),
+
+ /** Current time is after the allocation's end time. */
+ AFTER_END_TIME("AFTER_END_TIME"),
+
+ /** Subject was not selected due to traffic exposure percentage. */
+ TRAFFIC_EXPOSURE_MISS("TRAFFIC_EXPOSURE_MISS"),
+
+ /** Allocation was not evaluated (e.g., a previous allocation matched). */
+ UNEVALUATED("UNEVALUATED");
+
+ private final String code;
+
+ AllocationEvaluationCode(String code) {
+ this.code = code;
+ }
+
+ /** Returns the string representation of this allocation evaluation code. */
+ public String getCode() {
+ return code;
+ }
+
+ /**
+ * Parses a string code into an AllocationEvaluationCode enum.
+ *
+ * @param code the string code to parse
+ * @return the corresponding AllocationEvaluationCode, or null if not recognized
+ */
+ public static AllocationEvaluationCode fromString(String code) {
+ if (code == null) {
+ return null;
+ }
+ for (AllocationEvaluationCode evaluationCode : values()) {
+ if (evaluationCode.code.equals(code)) {
+ return evaluationCode;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return code;
+ }
+}
diff --git a/src/main/java/cloud/eppo/api/AssignmentDetails.java b/src/main/java/cloud/eppo/api/AssignmentDetails.java
new file mode 100644
index 0000000..5ad296b
--- /dev/null
+++ b/src/main/java/cloud/eppo/api/AssignmentDetails.java
@@ -0,0 +1,31 @@
+package cloud.eppo.api;
+
+/**
+ * Contains both the assigned variation value and comprehensive evaluation details explaining why
+ * that variation was assigned.
+ *
+ * @param The type of the variation value (Boolean, Integer, Double, String, etc.)
+ */
+public class AssignmentDetails {
+ private final T variation;
+ private final String action;
+ private final EvaluationDetails evaluationDetails;
+
+ public AssignmentDetails(T variation, String action, EvaluationDetails evaluationDetails) {
+ this.variation = variation;
+ this.action = action;
+ this.evaluationDetails = evaluationDetails;
+ }
+
+ public T getVariation() {
+ return variation;
+ }
+
+ public String getAction() {
+ return action;
+ }
+
+ public EvaluationDetails getEvaluationDetails() {
+ return evaluationDetails;
+ }
+}
diff --git a/src/main/java/cloud/eppo/api/EvaluationDetails.java b/src/main/java/cloud/eppo/api/EvaluationDetails.java
new file mode 100644
index 0000000..152ed2d
--- /dev/null
+++ b/src/main/java/cloud/eppo/api/EvaluationDetails.java
@@ -0,0 +1,271 @@
+package cloud.eppo.api;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Contains comprehensive debugging information about a flag evaluation. This includes why a
+ * particular variation was assigned, which allocations matched or didn't match, and other metadata
+ * useful for understanding flag behavior.
+ */
+public class EvaluationDetails {
+ private final String environmentName;
+ private final Date configFetchedAt;
+ private final Date configPublishedAt;
+ private final FlagEvaluationCode flagEvaluationCode;
+ private final String flagEvaluationDescription;
+ private final String banditKey;
+ private final String banditAction;
+ private final String variationKey;
+ private final EppoValue variationValue;
+ private final MatchedRule matchedRule;
+ private final AllocationDetails matchedAllocation;
+ private final List unmatchedAllocations;
+ private final List unevaluatedAllocations;
+
+ public EvaluationDetails(
+ String environmentName,
+ Date configFetchedAt,
+ Date configPublishedAt,
+ FlagEvaluationCode flagEvaluationCode,
+ String flagEvaluationDescription,
+ String banditKey,
+ String banditAction,
+ String variationKey,
+ EppoValue variationValue,
+ MatchedRule matchedRule,
+ AllocationDetails matchedAllocation,
+ List unmatchedAllocations,
+ List unevaluatedAllocations) {
+ this.environmentName = environmentName;
+ this.configFetchedAt = configFetchedAt;
+ this.configPublishedAt = configPublishedAt;
+ this.flagEvaluationCode = flagEvaluationCode;
+ this.flagEvaluationDescription = flagEvaluationDescription;
+ this.banditKey = banditKey;
+ this.banditAction = banditAction;
+ this.variationKey = variationKey;
+ this.variationValue = variationValue;
+ this.matchedRule = matchedRule;
+ this.matchedAllocation = matchedAllocation;
+ this.unmatchedAllocations = unmatchedAllocations;
+ this.unevaluatedAllocations = unevaluatedAllocations;
+ }
+
+ public String getEnvironmentName() {
+ return environmentName;
+ }
+
+ public Date getConfigFetchedAt() {
+ return configFetchedAt;
+ }
+
+ public Date getConfigPublishedAt() {
+ return configPublishedAt;
+ }
+
+ public FlagEvaluationCode getFlagEvaluationCode() {
+ return flagEvaluationCode;
+ }
+
+ public String getFlagEvaluationDescription() {
+ return flagEvaluationDescription;
+ }
+
+ public String getBanditKey() {
+ return banditKey;
+ }
+
+ public String getBanditAction() {
+ return banditAction;
+ }
+
+ public String getVariationKey() {
+ return variationKey;
+ }
+
+ public EppoValue getVariationValue() {
+ return variationValue;
+ }
+
+ public MatchedRule getMatchedRule() {
+ return matchedRule;
+ }
+
+ public AllocationDetails getMatchedAllocation() {
+ return matchedAllocation;
+ }
+
+ public List getUnmatchedAllocations() {
+ return unmatchedAllocations;
+ }
+
+ public List getUnevaluatedAllocations() {
+ return unevaluatedAllocations;
+ }
+
+ public boolean evaluationSuccessful() {
+ return !flagEvaluationCode.isError();
+ }
+
+ /** Creates a new Builder for constructing EvaluationDetails. */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Creates a default EvaluationDetails for error conditions or when no flag was matched. This is a
+ * convenience factory method for common error scenarios.
+ */
+ public static EvaluationDetails buildDefault(
+ String environmentName,
+ Date configFetchedAt,
+ Date configPublishedAt,
+ FlagEvaluationCode flagEvaluationCode,
+ String flagEvaluationDescription,
+ EppoValue variationValue) {
+ return builder()
+ .environmentName(environmentName)
+ .configFetchedAt(configFetchedAt)
+ .configPublishedAt(configPublishedAt)
+ .flagEvaluationCode(flagEvaluationCode)
+ .flagEvaluationDescription(flagEvaluationDescription)
+ .variationValue(variationValue)
+ .build();
+ }
+
+ /**
+ * Creates a new Builder initialized with values from an existing EvaluationDetails. Useful for
+ * creating a modified copy.
+ */
+ public static Builder builder(EvaluationDetails copyFrom) {
+ return new Builder()
+ .environmentName(copyFrom.environmentName)
+ .configFetchedAt(copyFrom.configFetchedAt)
+ .configPublishedAt(copyFrom.configPublishedAt)
+ .flagEvaluationCode(copyFrom.flagEvaluationCode)
+ .flagEvaluationDescription(copyFrom.flagEvaluationDescription)
+ .banditKey(copyFrom.banditKey)
+ .banditAction(copyFrom.banditAction)
+ .variationKey(copyFrom.variationKey)
+ .variationValue(copyFrom.variationValue)
+ .matchedRule(copyFrom.matchedRule)
+ .matchedAllocation(copyFrom.matchedAllocation)
+ .unmatchedAllocations(copyFrom.unmatchedAllocations)
+ .unevaluatedAllocations(copyFrom.unevaluatedAllocations);
+ }
+
+ /** Builder for constructing EvaluationDetails instances. */
+ public static class Builder {
+ private String environmentName = "Unknown";
+ private Date configFetchedAt;
+ private Date configPublishedAt;
+ private FlagEvaluationCode flagEvaluationCode;
+ private String flagEvaluationDescription;
+ private String banditKey;
+ private String banditAction;
+ private String variationKey;
+ private EppoValue variationValue;
+ private MatchedRule matchedRule;
+ private AllocationDetails matchedAllocation;
+ private List unmatchedAllocations = new ArrayList<>();
+ private List unevaluatedAllocations = new ArrayList<>();
+
+ public Builder environmentName(String environmentName) {
+ this.environmentName = environmentName != null ? environmentName : "Unknown";
+ return this;
+ }
+
+ public Builder configFetchedAt(Date configFetchedAt) {
+ this.configFetchedAt = configFetchedAt;
+ return this;
+ }
+
+ public Builder configPublishedAt(Date configPublishedAt) {
+ this.configPublishedAt = configPublishedAt;
+ return this;
+ }
+
+ public Builder flagEvaluationCode(FlagEvaluationCode flagEvaluationCode) {
+ this.flagEvaluationCode = flagEvaluationCode;
+ return this;
+ }
+
+ public Builder flagEvaluationDescription(String flagEvaluationDescription) {
+ this.flagEvaluationDescription = flagEvaluationDescription;
+ return this;
+ }
+
+ public Builder banditKey(String banditKey) {
+ this.banditKey = banditKey;
+ return this;
+ }
+
+ public Builder banditAction(String banditAction) {
+ this.banditAction = banditAction;
+ return this;
+ }
+
+ public Builder variationKey(String variationKey) {
+ this.variationKey = variationKey;
+ return this;
+ }
+
+ public Builder variationValue(EppoValue variationValue) {
+ this.variationValue = variationValue;
+ return this;
+ }
+
+ public Builder matchedRule(MatchedRule matchedRule) {
+ this.matchedRule = matchedRule;
+ return this;
+ }
+
+ public Builder matchedAllocation(AllocationDetails matchedAllocation) {
+ this.matchedAllocation = matchedAllocation;
+ return this;
+ }
+
+ public Builder unmatchedAllocations(List unmatchedAllocations) {
+ this.unmatchedAllocations =
+ unmatchedAllocations != null ? new ArrayList<>(unmatchedAllocations) : new ArrayList<>();
+ return this;
+ }
+
+ public Builder addUnmatchedAllocation(AllocationDetails allocation) {
+ this.unmatchedAllocations.add(allocation);
+ return this;
+ }
+
+ public Builder unevaluatedAllocations(List unevaluatedAllocations) {
+ this.unevaluatedAllocations =
+ unevaluatedAllocations != null
+ ? new ArrayList<>(unevaluatedAllocations)
+ : new ArrayList<>();
+ return this;
+ }
+
+ public Builder addUnevaluatedAllocation(AllocationDetails allocation) {
+ this.unevaluatedAllocations.add(allocation);
+ return this;
+ }
+
+ public EvaluationDetails build() {
+ return new EvaluationDetails(
+ environmentName,
+ configFetchedAt,
+ configPublishedAt,
+ flagEvaluationCode,
+ flagEvaluationDescription,
+ banditKey,
+ banditAction,
+ variationKey,
+ variationValue,
+ matchedRule,
+ matchedAllocation,
+ unmatchedAllocations,
+ unevaluatedAllocations);
+ }
+ }
+}
diff --git a/src/main/java/cloud/eppo/api/FlagEvaluationCode.java b/src/main/java/cloud/eppo/api/FlagEvaluationCode.java
new file mode 100644
index 0000000..1509e80
--- /dev/null
+++ b/src/main/java/cloud/eppo/api/FlagEvaluationCode.java
@@ -0,0 +1,74 @@
+package cloud.eppo.api;
+
+/**
+ * Enum representing the result code of a flag evaluation.
+ *
+ * Use {@link #isError()} to determine if the evaluation resulted in an error state.
+ */
+public enum FlagEvaluationCode {
+ /** Flag was successfully evaluated and a variation was assigned. */
+ MATCH("MATCH", false),
+
+ /** Flag was not found or is disabled. */
+ FLAG_UNRECOGNIZED_OR_DISABLED("FLAG_UNRECOGNIZED_OR_DISABLED", true),
+
+ /** The flag's type doesn't match the requested type. */
+ TYPE_MISMATCH("TYPE_MISMATCH", true),
+
+ /** The variation value is incompatible with the flag's declared type. */
+ ASSIGNMENT_ERROR("ASSIGNMENT_ERROR", true),
+
+ /** No allocations were configured for the flag. */
+ DEFAULT_ALLOCATION_NULL("DEFAULT_ALLOCATION_NULL", true),
+
+ /** Flag evaluation succeeded but bandit evaluation failed. */
+ BANDIT_ERROR("BANDIT_ERROR", true),
+
+ /** No actions were supplied for bandit evaluation. */
+ NO_ACTIONS_SUPPLIED_FOR_BANDIT("NO_ACTIONS_SUPPLIED_FOR_BANDIT", true);
+
+ private final String code;
+ private final boolean isError;
+
+ FlagEvaluationCode(String code, boolean isError) {
+ this.code = code;
+ this.isError = isError;
+ }
+
+ /** Returns the string representation of this evaluation code. */
+ public String getCode() {
+ return code;
+ }
+
+ /**
+ * Returns true if this evaluation code represents an error state.
+ *
+ * @return true if the evaluation failed and the default value should be used
+ */
+ public boolean isError() {
+ return isError;
+ }
+
+ /**
+ * Parses a string code into a FlagEvaluationCode enum.
+ *
+ * @param code the string code to parse
+ * @return the corresponding FlagEvaluationCode, or null if not recognized
+ */
+ public static FlagEvaluationCode fromString(String code) {
+ if (code == null) {
+ return null;
+ }
+ for (FlagEvaluationCode evaluationCode : values()) {
+ if (evaluationCode.code.equals(code)) {
+ return evaluationCode;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return code;
+ }
+}
diff --git a/src/main/java/cloud/eppo/api/MatchedRule.java b/src/main/java/cloud/eppo/api/MatchedRule.java
new file mode 100644
index 0000000..04230c3
--- /dev/null
+++ b/src/main/java/cloud/eppo/api/MatchedRule.java
@@ -0,0 +1,16 @@
+package cloud.eppo.api;
+
+import java.util.Set;
+
+/** Details about a rule that matched during allocation evaluation. */
+public class MatchedRule {
+ private final Set conditions;
+
+ public MatchedRule(Set conditions) {
+ this.conditions = conditions;
+ }
+
+ public Set getConditions() {
+ return conditions;
+ }
+}
diff --git a/src/main/java/cloud/eppo/api/RuleCondition.java b/src/main/java/cloud/eppo/api/RuleCondition.java
new file mode 100644
index 0000000..f5adf7b
--- /dev/null
+++ b/src/main/java/cloud/eppo/api/RuleCondition.java
@@ -0,0 +1,57 @@
+package cloud.eppo.api;
+
+import java.util.Objects;
+
+/** Represents a single condition within a targeting rule. */
+public class RuleCondition {
+ private final String attribute;
+ private final String operator;
+ private final EppoValue value;
+
+ public RuleCondition(String attribute, String operator, EppoValue value) {
+ this.attribute = attribute;
+ this.operator = operator;
+ this.value = value;
+ }
+
+ public String getAttribute() {
+ return attribute;
+ }
+
+ public String getOperator() {
+ return operator;
+ }
+
+ public EppoValue getValue() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ RuleCondition that = (RuleCondition) o;
+ return Objects.equals(attribute, that.attribute)
+ && Objects.equals(operator, that.operator)
+ && Objects.equals(value, that.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(attribute, operator, value);
+ }
+
+ @Override
+ public String toString() {
+ return "RuleCondition{"
+ + "attribute='"
+ + attribute
+ + '\''
+ + ", operator='"
+ + operator
+ + '\''
+ + ", value="
+ + value
+ + '}';
+ }
+}