From e0037b47a29f334b1dd534008cc10f0b80e6c435 Mon Sep 17 00:00:00 2001 From: alberthsuu Date: Thu, 10 Aug 2023 17:05:20 +0000 Subject: [PATCH] Update PrivacyQueryMapper for ACDP composition --- .../api/v2alpha/PrivacyQueryMapper.kt | 135 ++++++- .../loadtest/dataprovider/EdpSimulator.kt | 4 +- .../api/v2alpha/PrivacyQueryMapperTest.kt | 360 ++++++++++++++---- 3 files changed, 395 insertions(+), 104 deletions(-) diff --git a/src/main/kotlin/org/wfanet/measurement/eventdataprovider/privacybudgetmanagement/api/v2alpha/PrivacyQueryMapper.kt b/src/main/kotlin/org/wfanet/measurement/eventdataprovider/privacybudgetmanagement/api/v2alpha/PrivacyQueryMapper.kt index cecf98f16ce..3663d63defd 100644 --- a/src/main/kotlin/org/wfanet/measurement/eventdataprovider/privacybudgetmanagement/api/v2alpha/PrivacyQueryMapper.kt +++ b/src/main/kotlin/org/wfanet/measurement/eventdataprovider/privacybudgetmanagement/api/v2alpha/PrivacyQueryMapper.kt @@ -17,6 +17,9 @@ import org.wfanet.measurement.api.v2alpha.MeasurementSpec import org.wfanet.measurement.api.v2alpha.MeasurementSpec.MeasurementTypeCase import org.wfanet.measurement.api.v2alpha.RequisitionSpec import org.wfanet.measurement.common.toRange +import org.wfanet.measurement.eventdataprovider.noiser.DpParams +import org.wfanet.measurement.eventdataprovider.privacybudgetmanagement.AcdpParamsConverter +import org.wfanet.measurement.eventdataprovider.privacybudgetmanagement.AcdpQuery import org.wfanet.measurement.eventdataprovider.privacybudgetmanagement.DpCharge import org.wfanet.measurement.eventdataprovider.privacybudgetmanagement.DpQuery import org.wfanet.measurement.eventdataprovider.privacybudgetmanagement.EventGroupSpec @@ -24,12 +27,15 @@ import org.wfanet.measurement.eventdataprovider.privacybudgetmanagement.Landscap import org.wfanet.measurement.eventdataprovider.privacybudgetmanagement.Reference object PrivacyQueryMapper { + + private const val SENSITIVITY = 1.0 + /** * Constructs a pbm specific [DpQuery] from given proto messages. * * @param reference representing the reference key and if the charge is a refund. * @param measurementSpec The measurementSpec protobuf that is associated with the query. The VID - * sampling interval is obtained from from this. + * sampling interval is obtained from this. * @param eventSpecs event specs from the Requisition. The date range and demo groups are obtained * from this. * @throws @@ -37,7 +43,7 @@ object PrivacyQueryMapper { * if an error occurs in handling this request. Possible exceptions could include running out of * privacy budget or a failure to commit the transaction to the database. */ - fun getPrivacyQuery( + fun getDpQuery( reference: Reference, measurementSpec: MeasurementSpec, eventSpecs: Iterable @@ -56,21 +62,6 @@ object PrivacyQueryMapper { measurementSpec.reachAndFrequency.reachPrivacyParams.delta.toFloat() + measurementSpec.reachAndFrequency.frequencyPrivacyParams.delta.toFloat() ) - // TODO(@uakyol): After the privacy budget accounting is switched to using the Gaussian - // mechanism, replace the above lines with the following. This will further improve the - // efficiency of privacy budget usage for reach and frequency queries. - // - // { - // - // chargeList.add(PrivacyCharge( - // measurementSpec.reachAndFrequency.reachPrivacyParams.epsilon.toFloat(), - // measurementSpec.reachAndFrequency.reachPrivacyParams.delta.toFloat())) - // - // chargeList.add(PrivacyCharge( - // measurementSpec.reachAndFrequency.frequencyPrivacyParams.epsilon.toFloat(), - // measurementSpec.reachAndFrequency.frequencyPrivacyParams.delta.toFloat())) - // } - MeasurementTypeCase.IMPRESSION -> DpCharge( measurementSpec.impression.privacyParams.epsilon.toFloat(), @@ -90,7 +81,115 @@ object PrivacyQueryMapper { measurementSpec.vidSamplingInterval.start, measurementSpec.vidSamplingInterval.width ), - dpCharge + dpCharge, + ) + } + + fun getMpcAcdpQuery( + reference: Reference, + measurementSpec: MeasurementSpec, + eventSpecs: Iterable, + contributorCount: Int, + ): AcdpQuery { + val acdpCharge = + when (measurementSpec.measurementTypeCase) { + MeasurementTypeCase.REACH -> { + AcdpParamsConverter.getMpcAcdpCharge( + DpParams( + measurementSpec.reach.privacyParams.epsilon, + measurementSpec.reach.privacyParams.delta + ), + contributorCount, + ) + } + MeasurementTypeCase.REACH_AND_FREQUENCY -> { + val dpParams = + DpParams( + measurementSpec.reachAndFrequency.reachPrivacyParams.epsilon + + measurementSpec.reachAndFrequency.frequencyPrivacyParams.epsilon, + measurementSpec.reachAndFrequency.reachPrivacyParams.delta + + measurementSpec.reachAndFrequency.frequencyPrivacyParams.delta + ) + + AcdpParamsConverter.getMpcAcdpCharge( + dpParams, + contributorCount, + ) + } + else -> + throw IllegalArgumentException( + "Measurement type ${measurementSpec.measurementTypeCase} is not supported in getMpcAcdpQuery()" + ) + } + + return AcdpQuery( + reference, + LandscapeMask( + eventSpecs.map { EventGroupSpec(it.filter.expression, it.collectionInterval.toRange()) }, + measurementSpec.vidSamplingInterval.start, + measurementSpec.vidSamplingInterval.width + ), + acdpCharge + ) + } + + fun getDirectAcdpQuery( + reference: Reference, + measurementSpec: MeasurementSpec, + eventSpecs: Iterable, + ): AcdpQuery { + val acdpCharge = + when (measurementSpec.measurementTypeCase) { + MeasurementTypeCase.REACH -> { + AcdpParamsConverter.getDirectAcdpCharge( + DpParams( + measurementSpec.reach.privacyParams.epsilon, + measurementSpec.reach.privacyParams.delta + ), + SENSITIVITY, + ) + } + MeasurementTypeCase.REACH_AND_FREQUENCY -> { + val dpParams = + DpParams( + measurementSpec.reachAndFrequency.reachPrivacyParams.epsilon + + measurementSpec.reachAndFrequency.frequencyPrivacyParams.epsilon, + measurementSpec.reachAndFrequency.reachPrivacyParams.delta + + measurementSpec.reachAndFrequency.frequencyPrivacyParams.delta + ) + + AcdpParamsConverter.getDirectAcdpCharge( + dpParams, + SENSITIVITY, + ) + } + MeasurementTypeCase.IMPRESSION -> + AcdpParamsConverter.getDirectAcdpCharge( + DpParams( + measurementSpec.impression.privacyParams.epsilon, + measurementSpec.impression.privacyParams.delta + ), + SENSITIVITY, + ) + MeasurementTypeCase.DURATION -> + AcdpParamsConverter.getDirectAcdpCharge( + DpParams( + measurementSpec.duration.privacyParams.epsilon, + measurementSpec.duration.privacyParams.delta + ), + SENSITIVITY, + ) + else -> throw IllegalArgumentException("Measurement type not supported") + } + + return AcdpQuery( + reference, + LandscapeMask( + eventSpecs.map { EventGroupSpec(it.filter.expression, it.collectionInterval.toRange()) }, + measurementSpec.vidSamplingInterval.start, + measurementSpec.vidSamplingInterval.width + ), + acdpCharge, ) } } diff --git a/src/main/kotlin/org/wfanet/measurement/loadtest/dataprovider/EdpSimulator.kt b/src/main/kotlin/org/wfanet/measurement/loadtest/dataprovider/EdpSimulator.kt index 2720a5be3c3..894769ad47f 100644 --- a/src/main/kotlin/org/wfanet/measurement/loadtest/dataprovider/EdpSimulator.kt +++ b/src/main/kotlin/org/wfanet/measurement/loadtest/dataprovider/EdpSimulator.kt @@ -120,7 +120,7 @@ import org.wfanet.measurement.eventdataprovider.privacybudgetmanagement.PrivacyB import org.wfanet.measurement.eventdataprovider.privacybudgetmanagement.PrivacyBudgetManagerException import org.wfanet.measurement.eventdataprovider.privacybudgetmanagement.PrivacyBudgetManagerExceptionType import org.wfanet.measurement.eventdataprovider.privacybudgetmanagement.Reference -import org.wfanet.measurement.eventdataprovider.privacybudgetmanagement.api.v2alpha.PrivacyQueryMapper.getPrivacyQuery +import org.wfanet.measurement.eventdataprovider.privacybudgetmanagement.api.v2alpha.PrivacyQueryMapper.getDpQuery import org.wfanet.measurement.loadtest.config.TestIdentifiers.SIMULATOR_EVENT_GROUP_REFERENCE_ID_PREFIX import org.wfanet.measurement.loadtest.config.VidSampling @@ -673,7 +673,7 @@ class EdpSimulator( ) { try { privacyBudgetManager.chargePrivacyBudget( - getPrivacyQuery( + getDpQuery( Reference(measurementConsumerName, requisitionName, false), measurementSpec, eventSpecs diff --git a/src/test/kotlin/org/wfanet/measurement/eventdataprovider/privacybudgetmanagement/api/v2alpha/PrivacyQueryMapperTest.kt b/src/test/kotlin/org/wfanet/measurement/eventdataprovider/privacybudgetmanagement/api/v2alpha/PrivacyQueryMapperTest.kt index fe59a7b6c3b..91ad2ddc745 100644 --- a/src/test/kotlin/org/wfanet/measurement/eventdataprovider/privacybudgetmanagement/api/v2alpha/PrivacyQueryMapperTest.kt +++ b/src/test/kotlin/org/wfanet/measurement/eventdataprovider/privacybudgetmanagement/api/v2alpha/PrivacyQueryMapperTest.kt @@ -17,6 +17,7 @@ import com.google.common.truth.Truth.assertThat import com.google.type.interval import java.time.LocalDate import org.junit.Test +import org.wfanet.measurement.api.v2alpha.MeasurementSpecKt.duration import org.wfanet.measurement.api.v2alpha.MeasurementSpecKt.impression import org.wfanet.measurement.api.v2alpha.MeasurementSpecKt.reach import org.wfanet.measurement.api.v2alpha.MeasurementSpecKt.reachAndFrequency @@ -29,91 +30,26 @@ import org.wfanet.measurement.api.v2alpha.measurementSpec import org.wfanet.measurement.api.v2alpha.requisitionSpec import org.wfanet.measurement.common.OpenEndTimeRange import org.wfanet.measurement.common.toProtoTime +import org.wfanet.measurement.eventdataprovider.noiser.DpParams +import org.wfanet.measurement.eventdataprovider.privacybudgetmanagement.AcdpParamsConverter +import org.wfanet.measurement.eventdataprovider.privacybudgetmanagement.AcdpQuery import org.wfanet.measurement.eventdataprovider.privacybudgetmanagement.DpCharge import org.wfanet.measurement.eventdataprovider.privacybudgetmanagement.DpQuery import org.wfanet.measurement.eventdataprovider.privacybudgetmanagement.EventGroupSpec import org.wfanet.measurement.eventdataprovider.privacybudgetmanagement.LandscapeMask import org.wfanet.measurement.eventdataprovider.privacybudgetmanagement.Reference -import org.wfanet.measurement.eventdataprovider.privacybudgetmanagement.api.v2alpha.PrivacyQueryMapper.getPrivacyQuery - -private const val MEASUREMENT_CONSUMER_ID = "ACME" - -private val LAST_EVENT_DATE = LocalDate.now() -private val FIRST_EVENT_DATE = LAST_EVENT_DATE.minusDays(1) -private val TIME_RANGE = OpenEndTimeRange.fromClosedDateRange(FIRST_EVENT_DATE..LAST_EVENT_DATE) - -private const val FILTER_EXPRESSION = - "person.gender==0 && person.age_group==0 && banner_ad.gender == 1" -private val REQUISITION_SPEC = requisitionSpec { - eventGroups += eventGroupEntry { - key = "eventGroups/someEventGroup" - value = - RequisitionSpecKt.EventGroupEntryKt.value { - collectionInterval = interval { - startTime = TIME_RANGE.start.toProtoTime() - endTime = TIME_RANGE.endExclusive.toProtoTime() - } - filter = eventFilter { expression = FILTER_EXPRESSION } - } - } -} - -private val REACH_AND_FREQ_MEASUREMENT_SPEC = measurementSpec { - reachAndFrequency = reachAndFrequency { - reachPrivacyParams = differentialPrivacyParams { - epsilon = 0.3 - delta = 0.01 - } - - frequencyPrivacyParams = differentialPrivacyParams { - epsilon = 0.3 - delta = 0.01 - } - } - vidSamplingInterval = vidSamplingInterval { - start = 0.01f - width = 0.02f - } -} - -private val DURATION_MEASUREMENT_SPEC = measurementSpec { - impression = impression { - privacyParams = differentialPrivacyParams { - epsilon = 0.3 - delta = 0.02 - } - } -} - -private val IMPRESSION_MEASUREMENT_SPEC = measurementSpec { - impression = impression { - privacyParams = differentialPrivacyParams { - epsilon = 0.4 - delta = 0.02 - } - } -} - -private val REACH_MEASUREMENT_SPEC = measurementSpec { - reach = reach { - privacyParams = differentialPrivacyParams { - epsilon = 0.3 - delta = 0.01 - } - } - vidSamplingInterval = vidSamplingInterval { - start = 0.01f - width = 0.02f - } -} +import org.wfanet.measurement.eventdataprovider.privacybudgetmanagement.api.v2alpha.PrivacyQueryMapper.getDirectAcdpQuery +import org.wfanet.measurement.eventdataprovider.privacybudgetmanagement.api.v2alpha.PrivacyQueryMapper.getDpQuery +import org.wfanet.measurement.eventdataprovider.privacybudgetmanagement.api.v2alpha.PrivacyQueryMapper.getMpcAcdpQuery class PrivacyQueryMapperTest { + @Test - fun `converts reach and Frequency measurement to privacy query`() { - val referenceId = "RequisitioId1" + fun `converts reach and Frequency measurement to DpQuery`() { + val referenceId = "RequisitionId1" assertThat( - getPrivacyQuery( + getDpQuery( Reference(MEASUREMENT_CONSUMER_ID, referenceId, false), REACH_AND_FREQ_MEASUREMENT_SPEC, REQUISITION_SPEC.eventGroupsList.map { it.value } @@ -129,11 +65,11 @@ class PrivacyQueryMapperTest { } @Test - fun `converts duration measurement to privacy query`() { - val referenceId = "RequisitioId1" + fun `converts duration measurement to DpQuery`() { + val referenceId = "RequisitionId1" assertThat( - getPrivacyQuery( + getDpQuery( Reference(MEASUREMENT_CONSUMER_ID, referenceId, false), DURATION_MEASUREMENT_SPEC, REQUISITION_SPEC.eventGroupsList.map { it.value } @@ -149,11 +85,11 @@ class PrivacyQueryMapperTest { } @Test - fun `converts impression measurement to privacy query`() { - val referenceId = "RequisitioId1" + fun `converts impression measurement to DpQuery`() { + val referenceId = "RequisitionId1" assertThat( - getPrivacyQuery( + getDpQuery( Reference(MEASUREMENT_CONSUMER_ID, referenceId, false), IMPRESSION_MEASUREMENT_SPEC, REQUISITION_SPEC.eventGroupsList.map { it.value } @@ -169,11 +105,11 @@ class PrivacyQueryMapperTest { } @Test - fun `converts reach measurement to privacy query`() { - val referenceId = "RequisitioId1" + fun `converts reach measurement to DpQuery`() { + val referenceId = "RequisitionId1" assertThat( - getPrivacyQuery( + getDpQuery( Reference(MEASUREMENT_CONSUMER_ID, referenceId, false), REACH_MEASUREMENT_SPEC, REQUISITION_SPEC.eventGroupsList.map { it.value } @@ -194,4 +130,260 @@ class PrivacyQueryMapperTest { ) ) } + + @Test + fun `converts mpc reach and frequency measurement to AcdpQuery`() { + val referenceId = "RequisitionId1" + val dpParams = + DpParams( + REACH_AND_FREQ_MEASUREMENT_SPEC.reachAndFrequency.reachPrivacyParams.epsilon + + REACH_AND_FREQ_MEASUREMENT_SPEC.reachAndFrequency.frequencyPrivacyParams.epsilon, + REACH_AND_FREQ_MEASUREMENT_SPEC.reachAndFrequency.reachPrivacyParams.delta + + REACH_AND_FREQ_MEASUREMENT_SPEC.reachAndFrequency.frequencyPrivacyParams.delta + ) + val expectedAcdpCharge = AcdpParamsConverter.getMpcAcdpCharge(dpParams, CONTRIBUTOR_COUNT) + + assertThat( + getMpcAcdpQuery( + Reference(MEASUREMENT_CONSUMER_ID, referenceId, false), + REACH_AND_FREQ_MEASUREMENT_SPEC, + REQUISITION_SPEC.eventGroupsList.map { it.value }, + CONTRIBUTOR_COUNT + ) + ) + .isEqualTo( + AcdpQuery( + Reference(MEASUREMENT_CONSUMER_ID, referenceId, false), + LandscapeMask(listOf(EventGroupSpec(FILTER_EXPRESSION, TIME_RANGE)), 0.01f, 0.02f), + expectedAcdpCharge, + ) + ) + } + + @Test + fun `converts direct reach and frequency measurement to AcdpQuery`() { + val referenceId = "RequisitionId1" + val dpParams = + DpParams( + REACH_AND_FREQ_MEASUREMENT_SPEC.reachAndFrequency.reachPrivacyParams.epsilon + + REACH_AND_FREQ_MEASUREMENT_SPEC.reachAndFrequency.frequencyPrivacyParams.epsilon, + REACH_AND_FREQ_MEASUREMENT_SPEC.reachAndFrequency.reachPrivacyParams.delta + + REACH_AND_FREQ_MEASUREMENT_SPEC.reachAndFrequency.frequencyPrivacyParams.delta + ) + val expectedAcdpCharge = AcdpParamsConverter.getDirectAcdpCharge(dpParams, SENSITIVITY) + + assertThat( + getDirectAcdpQuery( + Reference(MEASUREMENT_CONSUMER_ID, referenceId, false), + REACH_AND_FREQ_MEASUREMENT_SPEC, + REQUISITION_SPEC.eventGroupsList.map { it.value } + ) + ) + .isEqualTo( + AcdpQuery( + Reference(MEASUREMENT_CONSUMER_ID, referenceId, false), + LandscapeMask(listOf(EventGroupSpec(FILTER_EXPRESSION, TIME_RANGE)), 0.01f, 0.02f), + expectedAcdpCharge, + ) + ) + } + + @Test + fun `converts mpc reach measurement to AcdpQuery`() { + val referenceId = "RequisitionId1" + + val expectedAcdpCharge = + AcdpParamsConverter.getMpcAcdpCharge( + DpParams( + REACH_MEASUREMENT_SPEC.reach.privacyParams.epsilon, + REACH_MEASUREMENT_SPEC.reach.privacyParams.delta + ), + CONTRIBUTOR_COUNT + ) + + assertThat( + getMpcAcdpQuery( + Reference(MEASUREMENT_CONSUMER_ID, referenceId, false), + REACH_MEASUREMENT_SPEC, + REQUISITION_SPEC.eventGroupsList.map { it.value }, + CONTRIBUTOR_COUNT + ) + ) + .isEqualTo( + AcdpQuery( + Reference(MEASUREMENT_CONSUMER_ID, referenceId, false), + LandscapeMask( + listOf(EventGroupSpec(FILTER_EXPRESSION, TIME_RANGE)), + REACH_MEASUREMENT_SPEC.vidSamplingInterval.start, + REACH_MEASUREMENT_SPEC.vidSamplingInterval.width + ), + expectedAcdpCharge, + ) + ) + } + + @Test + fun `converts direct reach measurement to AcdpQuery`() { + val referenceId = "RequisitionId1" + + val expectedAcdpCharge = + AcdpParamsConverter.getDirectAcdpCharge( + DpParams( + REACH_MEASUREMENT_SPEC.reach.privacyParams.epsilon, + REACH_MEASUREMENT_SPEC.reach.privacyParams.delta + ), + SENSITIVITY + ) + + assertThat( + getDirectAcdpQuery( + Reference(MEASUREMENT_CONSUMER_ID, referenceId, false), + REACH_MEASUREMENT_SPEC, + REQUISITION_SPEC.eventGroupsList.map { it.value } + ) + ) + .isEqualTo( + AcdpQuery( + Reference(MEASUREMENT_CONSUMER_ID, referenceId, false), + LandscapeMask( + listOf(EventGroupSpec(FILTER_EXPRESSION, TIME_RANGE)), + REACH_MEASUREMENT_SPEC.vidSamplingInterval.start, + REACH_MEASUREMENT_SPEC.vidSamplingInterval.width + ), + expectedAcdpCharge, + ) + ) + } + + @Test + fun `converts impression measurement to AcdpQuery`() { + val referenceId = "RequisitionId1" + + val expectedAcdpCharge = + AcdpParamsConverter.getDirectAcdpCharge( + DpParams( + IMPRESSION_MEASUREMENT_SPEC.impression.privacyParams.epsilon, + IMPRESSION_MEASUREMENT_SPEC.impression.privacyParams.delta + ), + SENSITIVITY + ) + + assertThat( + getDirectAcdpQuery( + Reference(MEASUREMENT_CONSUMER_ID, referenceId, false), + IMPRESSION_MEASUREMENT_SPEC, + REQUISITION_SPEC.eventGroupsList.map { it.value } + ) + ) + .isEqualTo( + AcdpQuery( + Reference(MEASUREMENT_CONSUMER_ID, referenceId, false), + LandscapeMask(listOf(EventGroupSpec(FILTER_EXPRESSION, TIME_RANGE)), 0.0f, 0.0f), + expectedAcdpCharge, + ) + ) + } + + @Test + fun `converts duration measurement to AcdpQuery`() { + val referenceId = "RequisitionId1" + + val expectedAcdpCharge = + AcdpParamsConverter.getDirectAcdpCharge( + DpParams( + DURATION_MEASUREMENT_SPEC.duration.privacyParams.epsilon, + DURATION_MEASUREMENT_SPEC.duration.privacyParams.delta + ), + SENSITIVITY + ) + + assertThat( + getDirectAcdpQuery( + Reference(MEASUREMENT_CONSUMER_ID, referenceId, false), + DURATION_MEASUREMENT_SPEC, + REQUISITION_SPEC.eventGroupsList.map { it.value } + ) + ) + .isEqualTo( + AcdpQuery( + Reference(MEASUREMENT_CONSUMER_ID, referenceId, false), + LandscapeMask(listOf(EventGroupSpec(FILTER_EXPRESSION, TIME_RANGE)), 0.0f, 0.0f), + expectedAcdpCharge, + ) + ) + } + + companion object { + private const val MEASUREMENT_CONSUMER_ID = "ACME" + private val LAST_EVENT_DATE = LocalDate.now() + private val FIRST_EVENT_DATE = LAST_EVENT_DATE.minusDays(1) + private val TIME_RANGE = OpenEndTimeRange.fromClosedDateRange(FIRST_EVENT_DATE..LAST_EVENT_DATE) + private const val FILTER_EXPRESSION = + "person.gender==0 && person.age_group==0 && banner_ad.gender == 1" + private const val CONTRIBUTOR_COUNT = 3 + private const val SENSITIVITY = 1.0 + + private val REQUISITION_SPEC = requisitionSpec { + eventGroups += eventGroupEntry { + key = "eventGroups/someEventGroup" + value = + RequisitionSpecKt.EventGroupEntryKt.value { + collectionInterval = interval { + startTime = TIME_RANGE.start.toProtoTime() + endTime = TIME_RANGE.endExclusive.toProtoTime() + } + filter = eventFilter { expression = FILTER_EXPRESSION } + } + } + } + + private val REACH_AND_FREQ_MEASUREMENT_SPEC = measurementSpec { + reachAndFrequency = reachAndFrequency { + reachPrivacyParams = differentialPrivacyParams { + epsilon = 0.3 + delta = 0.01 + } + + frequencyPrivacyParams = differentialPrivacyParams { + epsilon = 0.3 + delta = 0.01 + } + } + vidSamplingInterval = vidSamplingInterval { + start = 0.01f + width = 0.02f + } + } + + private val DURATION_MEASUREMENT_SPEC = measurementSpec { + duration = duration { + privacyParams = differentialPrivacyParams { + epsilon = 0.3 + delta = 0.02 + } + } + } + + private val IMPRESSION_MEASUREMENT_SPEC = measurementSpec { + impression = impression { + privacyParams = differentialPrivacyParams { + epsilon = 0.4 + delta = 0.02 + } + } + } + + private val REACH_MEASUREMENT_SPEC = measurementSpec { + reach = reach { + privacyParams = differentialPrivacyParams { + epsilon = 0.3 + delta = 0.01 + } + } + vidSamplingInterval = vidSamplingInterval { + start = 0.01f + width = 0.02f + } + } + } }