ScenarioSource.java

package org.hammer.audio.experimental.acoustic.scenario;

import java.util.Objects;

/**
 * Ground truth for a single acoustic source in a scenario.
 *
 * <p>{@link #trajectory()}, {@link #acousticProperties()} and {@link #labels()} may be {@code null}
 * to represent partial ground truth. This is expected for real-world datasets where position or
 * classification may not be known.
 *
 * <p>Use {@link Builder} for incremental construction when not all fields are available upfront.
 *
 * @param sourceId unique source identifier within the scenario
 * @param sourceType source category (for example {@code "mosquito"} or {@code "bird"})
 * @param trajectory source trajectory ground truth (nullable for partial truth)
 * @param acousticProperties source acoustic ground truth (nullable for partial truth)
 * @param labels source classification ground truth (nullable for partial truth)
 */
public record ScenarioSource(
    String sourceId,
    String sourceType,
    ScenarioTrajectory trajectory,
    AcousticGroundTruth acousticProperties,
    ClassificationGroundTruth labels) {

  /* Validate required fields. */
  public ScenarioSource {
    Objects.requireNonNull(sourceId, "sourceId");
    Objects.requireNonNull(sourceType, "sourceType");
    if (sourceId.isBlank()) {
      throw new IllegalArgumentException("sourceId must not be blank");
    }
    if (sourceType.isBlank()) {
      throw new IllegalArgumentException("sourceType must not be blank");
    }
  }

  /**
   * Create a {@link Builder} for the given source id and type.
   *
   * @param sourceId unique source identifier within the scenario; must not be blank
   * @param sourceType category string (e.g. {@code "mosquito"}, {@code "bird"}); must not be blank
   */
  public static Builder builder(String sourceId, String sourceType) {
    return new Builder(sourceId, sourceType);
  }

  /** Incremental builder for {@link ScenarioSource}. */
  public static final class Builder {

    private final String sourceId;
    private final String sourceType;
    private ScenarioTrajectory trajectoryValue;
    private AcousticGroundTruth acousticPropertiesValue;
    private ClassificationGroundTruth labelsValue;

    private Builder(String sourceId, String sourceType) {
      this.sourceId = Objects.requireNonNull(sourceId, "sourceId");
      this.sourceType = Objects.requireNonNull(sourceType, "sourceType");
    }

    /** Set the trajectory ground truth; {@code null} is permitted for partial truth. */
    public Builder trajectory(ScenarioTrajectory trajectory) {
      this.trajectoryValue = trajectory;
      return this;
    }

    /** Set the acoustic ground truth; {@code null} is permitted for partial truth. */
    public Builder acousticProperties(AcousticGroundTruth acousticProperties) {
      this.acousticPropertiesValue = acousticProperties;
      return this;
    }

    /** Set the classification ground truth; {@code null} is permitted for partial truth. */
    public Builder labels(ClassificationGroundTruth labels) {
      this.labelsValue = labels;
      return this;
    }

    /** Build the {@link ScenarioSource}. */
    public ScenarioSource build() {
      return new ScenarioSource(
          sourceId, sourceType, trajectoryValue, acousticPropertiesValue, labelsValue);
    }
  }
}