[reland] JSON source generator includes error estimates

> Initially, an error estimate is included for each privacy level
> using a default population of 100K.

To prevent google3 test failure, unit tests now
construct an error calculator using a string containing
the testParamRecords rather than a file path.

Change-Id: I5e02f92b74363b0ecae70ce8db356ec9b7e6ab01
Reviewed-on: https://fuchsia-review.googlesource.com/c/cobalt/+/466454
Reviewed-by: Alexandre Zani <azani@google.com>
Commit-Queue: Jared Weinstein <jaredweinstein@google.com>
diff --git a/src/bin/config_parser/src/privacy/error_calculator.go b/src/bin/config_parser/src/privacy/error_calculator.go
index dfd6cf5..2dff690 100644
--- a/src/bin/config_parser/src/privacy/error_calculator.go
+++ b/src/bin/config_parser/src/privacy/error_calculator.go
@@ -11,7 +11,7 @@
 )
 
 type ErrorCalculator struct {
-	calc PrivacyEncodingParamsCalculator
+	ParamsCalc PrivacyEncodingParamsCalculator
 }
 
 // Public factory method for creating an ErrorCalculator given a
@@ -20,6 +20,20 @@
 	return &ErrorCalculator{paramsCalc}
 }
 
+// Public factory method for creating an ErrorCalculator given the file path
+// of the PrivacyEncodingParams.
+func NewErrorCalculatorFromPrivacyParams(privacyParamsPath string) (*ErrorCalculator, error) {
+	paramsCalculator, err := NewPrivacyEncodingParamsCalculator(privacyParamsPath)
+	if err != nil {
+		return nil, err
+	}
+	errorCalculator := NewErrorCalculator(*paramsCalculator)
+	if err != nil {
+		return nil, err
+	}
+	return errorCalculator, nil
+}
+
 // Given a |metric|, |report|, and |params|, estimates the report row error.
 //
 // TODO(jaredweinstein): implement estimates for other report types.
@@ -31,8 +45,8 @@
 		return -1, err
 	}
 
-	populationConstant := e.calc.Constants.population
-	privacyEncodingParams, err := e.calc.GetPrivacyEncodingParams(epsilon, populationConstant, sparsity)
+	populationConstant := e.ParamsCalc.Constants.population
+	privacyEncodingParams, err := e.ParamsCalc.GetPrivacyEncodingParams(epsilon, populationConstant, sparsity)
 	if err != nil {
 		return -1, err
 	}
diff --git a/src/bin/config_parser/src/source_generator/json.go b/src/bin/config_parser/src/source_generator/json.go
index 3f974c6..06bb4ed 100644
--- a/src/bin/config_parser/src/source_generator/json.go
+++ b/src/bin/config_parser/src/source_generator/json.go
@@ -8,8 +8,13 @@
 import (
 	"config"
 	"encoding/json"
+	"privacy"
 )
 
+type jsonOutputter struct {
+	errorCalculator *privacy.ErrorCalculator
+}
+
 // JSON export structure
 // go tags set the field name when exported to JSON data.
 type jsonRegistry struct {
@@ -48,25 +53,66 @@
 	Reports             []jsonReport `json:"reports"`
 }
 
+type jsonError map[string]jsonErrorEstimate
+
 type jsonReport struct {
-	Name                                 string   `json:"name"`
-	Id                                   uint32   `json:"id"`
-	ReportType                           string   `json:"report_type"`
-	ReportTypeId                         int32    `json:"report_type_id"`
-	LocalPrivacyNoiseLevel               string   `json:"local_privacy_noise_level"`
-	PrivacyLevel                         string   `json:"privacy_level"`
-	CandidateFile                        string   `json:"candidate_file"`
-	CandidateList                        []string `json:"candidate_list"`
-	SystemProfileField                   []string `json:"system_profile_field"`
-	AggregationType                      string   `json:"aggregation_type"`
-	WindowSize                           []string `json:"window_size"`
-	LocalAggregationPeriod               int32    `json:"local_aggregation_period"`
-	LocalAggregationProcedure            string   `json:"local_aggregation_procedure"`
-	LocalAggregationProcedurePercentileN uint32   `json:"local_aggregation_procedure_percentile_n"`
+	Name                                 string    `json:"name"`
+	Id                                   uint32    `json:"id"`
+	ReportType                           string    `json:"report_type"`
+	ReportTypeId                         int32     `json:"report_type_id"`
+	LocalPrivacyNoiseLevel               string    `json:"local_privacy_noise_level"`
+	PrivacyLevel                         string    `json:"privacy_level"`
+	ErrorEstimates                       jsonError `json:"error_estimates"`
+	CandidateFile                        string    `json:"candidate_file"`
+	CandidateList                        []string  `json:"candidate_list"`
+	SystemProfileField                   []string  `json:"system_profile_field"`
+	AggregationType                      string    `json:"aggregation_type"`
+	WindowSize                           []string  `json:"window_size"`
+	LocalAggregationPeriod               int32     `json:"local_aggregation_period"`
+	LocalAggregationProcedure            string    `json:"local_aggregation_procedure"`
+	LocalAggregationProcedurePercentileN uint32    `json:"local_aggregation_procedure_percentile_n"`
+}
+
+type jsonErrorEstimate struct {
+	Population uint32  `json:"population"`
+	Epsilon    float64 `json:"epsilon"`
+	Estimate   float64 `json:"estimate"`
+}
+
+func (jo *jsonOutputter) makeErrorEstimates(report *config.ReportDefinition, metric *config.MetricDefinition) (estimate jsonError) {
+	if jo.errorCalculator == nil {
+		// Error calculator not provided; error estimation skipped.
+		return nil
+	}
+
+	population := report.GetReportingThreshold()
+	if population == 0 {
+		population = 100000
+	}
+
+	var estimates = jsonError{}
+	for l, _ := range config.ReportDefinition_PrivacyLevel_name {
+		level := config.ReportDefinition_PrivacyLevel(l)
+		epsilon := jo.errorCalculator.ParamsCalc.Constants.EpsilonForPrivacyLevel[level]
+		errorValue, err := jo.errorCalculator.Estimate(metric, report, epsilon, uint64(population))
+		if err == nil {
+			estimates[level.String()] = jsonErrorEstimate{
+				Population: population,
+				Epsilon:    epsilon,
+				Estimate:   errorValue,
+			}
+		}
+	}
+	if len(estimates) == 0 {
+		// Most likely, error estimation is not supported for this specific
+		// report/metric configuration.
+		return nil
+	}
+	return estimates
 }
 
 // JSON struct constructors
-func makeJSONReport(report *config.ReportDefinition) jsonReport {
+func (jo *jsonOutputter) makeJSONReport(report *config.ReportDefinition, metric *config.MetricDefinition) jsonReport {
 	if report == nil {
 		return jsonReport{}
 	}
@@ -79,9 +125,10 @@
 	var windowSize []string
 	for _, ws := range report.GetWindowSize() {
 		windowSize = append(windowSize, ws.String())
-
 	}
 
+	estimate := jo.makeErrorEstimates(report, metric)
+
 	return jsonReport{
 		Name:                                 report.GetReportName(),
 		Id:                                   report.GetId(),
@@ -89,6 +136,7 @@
 		ReportTypeId:                         int32(report.GetReportType()),
 		LocalPrivacyNoiseLevel:               report.GetLocalPrivacyNoiseLevel().String(),
 		PrivacyLevel:                         report.GetPrivacyLevel().String(),
+		ErrorEstimates:                       estimate,
 		CandidateFile:                        report.GetCandidateFile(),
 		CandidateList:                        report.GetCandidateList(),
 		SystemProfileField:                   systemProfileField,
@@ -100,7 +148,7 @@
 	}
 }
 
-func makeJSONMetric(metric *config.MetricDefinition) jsonMetric {
+func (jo *jsonOutputter) makeJSONMetric(metric *config.MetricDefinition) jsonMetric {
 	if metric == nil {
 		return jsonMetric{}
 	}
@@ -113,7 +161,7 @@
 	}
 	var reports []jsonReport
 	for _, r := range metric.GetReports() {
-		reports = append(reports, makeJSONReport(r))
+		reports = append(reports, jo.makeJSONReport(r, metric))
 	}
 
 	var dimensions []string
@@ -148,14 +196,14 @@
 	}
 }
 
-func makeJSONProject(project *config.ProjectConfig) jsonProject {
+func (jo *jsonOutputter) makeJSONProject(project *config.ProjectConfig) jsonProject {
 	if project == nil {
 		return jsonProject{}
 	}
 
 	var metrics []jsonMetric
 	for _, m := range project.GetMetrics() {
-		metrics = append(metrics, makeJSONMetric(m))
+		metrics = append(metrics, jo.makeJSONMetric(m))
 	}
 
 	return jsonProject{
@@ -166,14 +214,14 @@
 	}
 }
 
-func makeJSONCustomer(customer *config.CustomerConfig) jsonCustomer {
+func (jo *jsonOutputter) makeJSONCustomer(customer *config.CustomerConfig) jsonCustomer {
 	if customer == nil {
 		return jsonCustomer{}
 	}
 
 	var projects []jsonProject
 	for _, p := range customer.GetProjects() {
-		projects = append(projects, makeJSONProject(p))
+		projects = append(projects, jo.makeJSONProject(p))
 	}
 
 	return jsonCustomer{
@@ -183,14 +231,14 @@
 	}
 }
 
-func makeJSONRegistry(registry *config.CobaltRegistry) jsonRegistry {
+func (jo *jsonOutputter) makeJSONRegistry(registry *config.CobaltRegistry) jsonRegistry {
 	if registry == nil {
 		return jsonRegistry{}
 	}
 
 	var customers []jsonCustomer
 	for _, c := range registry.GetCustomers() {
-		customers = append(customers, makeJSONCustomer(c))
+		customers = append(customers, jo.makeJSONCustomer(c))
 	}
 
 	return jsonRegistry{
@@ -199,8 +247,8 @@
 }
 
 // Outputs the registry contents in JSON
-func JSONOutput(_, filtered *config.CobaltRegistry) (outputBytes []byte, err error) {
-	jsonRegistry := makeJSONRegistry(filtered)
+func (jo *jsonOutputter) JSONOutput(_, filtered *config.CobaltRegistry) (outputBytes []byte, err error) {
+	jsonRegistry := jo.makeJSONRegistry(filtered)
 
 	prefix := ""
 	indent := "  "
@@ -211,3 +259,19 @@
 
 	return jsonBytes, nil
 }
+
+// TODO: (jaredweinstein) Remove this deprecated method once call sites are switched.
+func JSONOutput(_, filtered *config.CobaltRegistry) (outputBytes []byte, err error) {
+	// Invalid privacy param path only prevents error estimate struct from being
+	// properly populated.
+	jo := jsonOutputter{nil}
+	return jo.JSONOutput(nil, filtered)
+}
+
+// Returns an output formatter for JSON
+//
+// privacyParamsPath is the string path of the privacy params file to be used for error estimation.
+func JSONOutputFactory(errorCalculator *privacy.ErrorCalculator) OutputFormatter {
+	jo := jsonOutputter{errorCalculator}
+	return jo.JSONOutput
+}
diff --git a/src/bin/config_parser/src/source_generator/json_test.go b/src/bin/config_parser/src/source_generator/json_test.go
index b700ea5..1d3eb63 100644
--- a/src/bin/config_parser/src/source_generator/json_test.go
+++ b/src/bin/config_parser/src/source_generator/json_test.go
@@ -8,34 +8,96 @@
 
 import (
 	"config"
+	"privacy"
 	"reflect"
 	"testing"
 )
 
+var testParamRecords = [][]string{
+	{"1.0", "10000", "1", "0.0024182694032788277", "9"},
+	{"1.0", "20000", "1", "0.0013450710102915764", "10"},
+	{"1.0", "10000", "2", "0.004249398596584797", "7"},
+	{"1.0", "20000", "2", "0.002368534915149212", "9"},
+	{"1.0", "10000", "10", "0.019537480548024178", "4"},
+	{"1.0", "20000", "10", "0.011127800680696964", "5"},
+	{"5.0", "10000", "1", "0.000906200148165226", "12"},
+	{"5.0", "20000", "1", "0.000491277314722538", "15"},
+	{"5.0", "10000", "2", "0.0009743086993694305", "12"},
+	{"5.0", "20000", "2", "0.0005256505683064461", "14"},
+	{"5.0", "10000", "10", "0.0014028092846274376", "10"},
+	{"5.0", "20000", "10", "0.0007524723187088966", "13"},
+}
+
+func constructTestJsonOutputter(t *testing.T) jsonOutputter {
+	paramsCalc, err := privacy.NewPrivacyEncodingParamsCalculatorForTesting(testParamRecords)
+	if err != nil {
+		t.Errorf("Failed to create error calculator.")
+	}
+	errorCalc := privacy.NewErrorCalculator(*paramsCalc)
+	return jsonOutputter{errorCalc}
+}
+
 func TestConstructorsHandleNil(t *testing.T) {
-	r := makeJSONReport(nil)
+	jo := constructTestJsonOutputter(t)
+	r := jo.makeJSONReport(nil, nil)
 	if reflect.DeepEqual(r, (jsonReport{})) == false {
 		t.Errorf("makeJSONReport failed to return empty report got = %#v", r)
 	}
-	m := makeJSONMetric(nil)
+	m := jo.makeJSONMetric(nil)
 	if reflect.DeepEqual(m, (jsonMetric{})) == false {
 		t.Errorf("makeJSONMetric failed to return empty metric got = %#v", m)
 	}
-	p := makeJSONProject(nil)
+	p := jo.makeJSONProject(nil)
 	if reflect.DeepEqual(p, (jsonProject{})) == false {
 		t.Errorf("makeJSONProject failed to return empty project got = %#v", p)
 	}
-	c := makeJSONCustomer(nil)
+	c := jo.makeJSONCustomer(nil)
 	if reflect.DeepEqual(c, (jsonCustomer{})) == false {
 		t.Errorf("makeJSONCustomer failed to return empty customer got = %#v", c)
 	}
-	rg := makeJSONRegistry(nil)
+	rg := jo.makeJSONRegistry(nil)
 	if reflect.DeepEqual(rg, (jsonRegistry{})) == false {
 		t.Errorf("makeJSONRegistry failed to return empty registry got = %#v", rg)
 	}
 }
 
+func TestMakeErrorEstimates(t *testing.T) {
+	jo := constructTestJsonOutputter(t)
+	r := config.ReportDefinition{
+		ReportType:                config.ReportDefinition_UNIQUE_DEVICE_COUNTS,
+		PrivacyLevel:              config.ReportDefinition_LOW_PRIVACY,
+		LocalAggregationProcedure: config.ReportDefinition_SELECT_FIRST,
+	}
+	m := config.MetricDefinition{
+		MetricType: config.MetricDefinition_OCCURRENCE,
+	}
+
+	want := jsonError{
+		"LOW_PRIVACY": jsonErrorEstimate{
+			Population: 100000,
+			Epsilon:    10,
+			Estimate:   9.532416856027137,
+		},
+		"MEDIUM_PRIVACY": jsonErrorEstimate{
+			Population: 100000,
+			Epsilon:    5,
+			Estimate:   9.532416856027137,
+		},
+		"HIGH_PRIVACY": jsonErrorEstimate{
+			Population: 100000,
+			Epsilon:    1,
+			Estimate:   15.607457540599944,
+		},
+	}
+
+	got := jo.makeErrorEstimates(&r, &m)
+	if reflect.DeepEqual(want, got) == false {
+		t.Errorf("makeJSONReport(%#v)\n\n GOT: %#v\nWANT: %#v", r, got, want)
+	}
+}
+
 func TestMakeJSONReport(t *testing.T) {
+	jo := constructTestJsonOutputter(t)
 	name := "test_name"
 	id := uint32(123456789)
 	candidate_file := "test_file_name"
@@ -58,6 +120,11 @@
 		LocalAggregationProcedure:            config.ReportDefinition_SUM_PROCEDURE,
 		LocalAggregationProcedurePercentileN: aggregation_percentile,
 	}
+	m := config.MetricDefinition{
+		MetricName: name,
+		Id:         id,
+		MetricType: config.MetricDefinition_EVENT_OCCURRED,
+	}
 
 	want := jsonReport{
 		Name:                                 name,
@@ -66,6 +133,7 @@
 		ReportTypeId:                         int32(config.ReportDefinition_SIMPLE_OCCURRENCE_COUNT),
 		LocalPrivacyNoiseLevel:               "NOISE_LEVEL_UNSET",
 		PrivacyLevel:                         "LOW_PRIVACY",
+		ErrorEstimates:                       nil,
 		CandidateFile:                        candidate_file,
 		CandidateList:                        candidate_list,
 		SystemProfileField:                   []string{"OS", "ARCH"},
@@ -76,13 +144,14 @@
 		LocalAggregationProcedurePercentileN: aggregation_percentile,
 	}
 
-	got := makeJSONReport(&r)
+	got := jo.makeJSONReport(&r, &m)
 	if reflect.DeepEqual(want, got) == false {
 		t.Errorf("makeJSONReport(%#v)\n\n GOT: %#v\nWANT: %#v", r, got, want)
 	}
 }
 
 func TestMakeJSONMetric(t *testing.T) {
+	jo := constructTestJsonOutputter(t)
 	name := "test_name"
 	id := uint32(123456789)
 	owners := []string{"owners", "owner2"}
@@ -145,13 +214,14 @@
 		Reports:             emptyReports,
 	}
 
-	got := makeJSONMetric(&m)
+	got := jo.makeJSONMetric(&m)
 	if reflect.DeepEqual(want, got) == false {
 		t.Errorf("makeJSONMetric(%#v)\n\n GOT: %#v\nWANT: %#v", m, got, want)
 	}
 }
 
 func TestMakeJSONMetricEmptyMetadata(t *testing.T) {
+	jo := constructTestJsonOutputter(t)
 	name := "test_name"
 	reports := []*config.ReportDefinition{nil, nil}
 	m := config.MetricDefinition{
@@ -168,13 +238,14 @@
 		Reports:    emptyReports,
 	}
 
-	got := makeJSONMetric(&m)
+	got := jo.makeJSONMetric(&m)
 	if reflect.DeepEqual(want, got) == false {
 		t.Errorf("makeJSONMetric(%#v)\n\n GOT: %#v\nWANT: %#v", m, got, want)
 	}
 }
 
 func TestMakeJSONProject(t *testing.T) {
+	jo := constructTestJsonOutputter(t)
 	name := "test_name"
 	id := uint32(123456789)
 	metrics := []*config.MetricDefinition{nil, nil}
@@ -194,13 +265,14 @@
 		Metrics: emptyMetrics,
 	}
 
-	got := makeJSONProject(&p)
+	got := jo.makeJSONProject(&p)
 	if reflect.DeepEqual(want, got) == false {
 		t.Errorf("makeJSONProject(%#v)\n\n GOT: %#v\nWANT: %#v", p, got, want)
 	}
 }
 
 func TestMakeJSONCustomer(t *testing.T) {
+	jo := constructTestJsonOutputter(t)
 	name := "test_name"
 	id := uint32(123456789)
 	projects := []*config.ProjectConfig{nil, nil}
@@ -217,24 +289,25 @@
 		Projects: emptyProjects,
 	}
 
-	got := makeJSONCustomer(&c)
+	got := jo.makeJSONCustomer(&c)
 	if reflect.DeepEqual(want, got) == false {
 		t.Errorf("makeJSONCustomer(%#v)\n\n GOT: %#v\nWANT: %#v", c, got, want)
 	}
 }
 
 func TestMakeJSONRegistry(t *testing.T) {
+	jo := constructTestJsonOutputter(t)
 	customers := []*config.CustomerConfig{nil, nil}
 	r := config.CobaltRegistry{
 		Customers: customers,
 	}
 
-	emptyProjects := []jsonCustomer{jsonCustomer{}, jsonCustomer{}}
+	emptyCustomers := []jsonCustomer{jsonCustomer{}, jsonCustomer{}}
 	want := jsonRegistry{
-		Customers: emptyProjects,
+		Customers: emptyCustomers,
 	}
 
-	got := makeJSONRegistry(&r)
+	got := jo.makeJSONRegistry(&r)
 	if reflect.DeepEqual(want, got) == false {
 		t.Errorf("makeJSONRegistry(%#v)\n\n GOT: %#v\nWANT: %#v", r, got, want)
 	}
diff --git a/src/bin/config_parser/src/source_generator/source_generator_test.go b/src/bin/config_parser/src/source_generator/source_generator_test.go
index 614f62f..6006ab3 100644
--- a/src/bin/config_parser/src/source_generator/source_generator_test.go
+++ b/src/bin/config_parser/src/source_generator/source_generator_test.go
@@ -197,7 +197,7 @@
 	{"GoldenForTesting.java", JavaOutputFactory("config", []string{}, []string{"testing"}, "golden_for_testing"), false},
 
 	// JSONOutput() ignores hideOnClients so we only test once
-	{"golden.cb.json", JSONOutput, true},
+	{"golden.cb.json", JSONOutputFactory(nil), true},
 }
 
 func TestPrintConfig(t *testing.T) {
diff --git a/src/bin/config_parser/src/source_generator/source_generator_test_files/golden.cb.json b/src/bin/config_parser/src/source_generator/source_generator_test_files/golden.cb.json
index 0cf15d6..3e54111 100644
--- a/src/bin/config_parser/src/source_generator/source_generator_test_files/golden.cb.json
+++ b/src/bin/config_parser/src/source_generator/source_generator_test_files/golden.cb.json
@@ -33,6 +33,7 @@
                   "report_type_id": 9999,
                   "local_privacy_noise_level": "NOISE_LEVEL_UNSET",
                   "privacy_level": "PRIVACY_LEVEL_UNKNOWN",
+                  "error_estimates": null,
                   "candidate_file": "",
                   "candidate_list": null,
                   "system_profile_field": null,
@@ -49,6 +50,7 @@
                   "report_type_id": 3,
                   "local_privacy_noise_level": "NOISE_LEVEL_UNSET",
                   "privacy_level": "PRIVACY_LEVEL_UNKNOWN",
+                  "error_estimates": null,
                   "candidate_file": "",
                   "candidate_list": null,
                   "system_profile_field": null,
@@ -88,6 +90,7 @@
                   "report_type_id": 1,
                   "local_privacy_noise_level": "NOISE_LEVEL_UNSET",
                   "privacy_level": "PRIVACY_LEVEL_UNKNOWN",
+                  "error_estimates": null,
                   "candidate_file": "",
                   "candidate_list": null,
                   "system_profile_field": null,
@@ -131,6 +134,7 @@
                   "report_type_id": 1,
                   "local_privacy_noise_level": "NOISE_LEVEL_UNSET",
                   "privacy_level": "LOW_PRIVACY",
+                  "error_estimates": null,
                   "candidate_file": "",
                   "candidate_list": null,
                   "system_profile_field": null,
@@ -184,6 +188,7 @@
                   "report_type_id": 0,
                   "local_privacy_noise_level": "NOISE_LEVEL_UNSET",
                   "privacy_level": "PRIVACY_LEVEL_UNKNOWN",
+                  "error_estimates": null,
                   "candidate_file": "",
                   "candidate_list": null,
                   "system_profile_field": null,
diff --git a/src/bin/config_parser/src/source_generator/source_outputter.go b/src/bin/config_parser/src/source_generator/source_outputter.go
index 2ab9b8f..003add5 100644
--- a/src/bin/config_parser/src/source_generator/source_outputter.go
+++ b/src/bin/config_parser/src/source_generator/source_outputter.go
@@ -10,6 +10,7 @@
 	"bytes"
 	"config"
 	"fmt"
+	"privacy"
 	"reflect"
 	"sort"
 	"strconv"
@@ -442,7 +443,7 @@
 	}
 }
 
-func getOutputFormatter(format, namespace, goPackageName, varName string, features []string, outFilename string) (OutputFormatter, error) {
+func getOutputFormatter(format, namespace, goPackageName, varName string, features []string, outFilename string, errorCalculator *privacy.ErrorCalculator) (OutputFormatter, error) {
 	namespaceList := []string{}
 	if namespace != "" {
 		namespaceList = strings.Split(namespace, ".")
@@ -464,7 +465,7 @@
 	case "java":
 		return JavaOutputFactory(varName, namespaceList, features, outFilename), nil
 	case "json":
-		return JSONOutput, nil
+		return JSONOutputFactory(errorCalculator), nil
 	case "rust":
 		return RustOutputFactory(varName, namespaceList, features), nil
 	default:
diff --git a/src/bin/config_parser/src/source_generator/source_outputter_test.go b/src/bin/config_parser/src/source_generator/source_outputter_test.go
index f8df45d..b6d04ed 100644
--- a/src/bin/config_parser/src/source_generator/source_outputter_test.go
+++ b/src/bin/config_parser/src/source_generator/source_outputter_test.go
@@ -4,13 +4,17 @@
 
 package source_generator
 
-import "testing"
+import (
+	"privacy"
+	"testing"
+)
 
 func TestGetOutputFormatter(t *testing.T) {
 	formats := []string{"bin", "b64", "cpp", "dart", "rust", "go", "java", "json"}
+	errorCalculator := privacy.ErrorCalculator{privacy.PrivacyEncodingParamsCalculator{}}
 
 	for _, format := range formats {
-		outputFormatter, err := getOutputFormatter(format, "ns", "package", "varName", []string{}, "")
+		outputFormatter, err := getOutputFormatter(format, "ns", "package", "varName", []string{}, "", &errorCalculator)
 		if outputFormatter == nil {
 			t.Errorf("Unexpected nil output formatter for format %v", format)
 		}
@@ -19,7 +23,7 @@
 		}
 	}
 
-	outputFormatter, err := getOutputFormatter("blah", "ns", "package", "varName", []string{}, "")
+	outputFormatter, err := getOutputFormatter("invalid_format", "ns", "package", "varName", []string{}, "", &errorCalculator)
 	if outputFormatter != nil {
 		t.Errorf("Unexpectedly got an output formatter.")
 	}
diff --git a/src/bin/config_parser/src/source_generator/writer.go b/src/bin/config_parser/src/source_generator/writer.go
index e485458..fa7c43e 100644
--- a/src/bin/config_parser/src/source_generator/writer.go
+++ b/src/bin/config_parser/src/source_generator/writer.go
@@ -11,22 +11,24 @@
 	"flag"
 	"fmt"
 	"os"
+	"privacy"
 	"strings"
 )
 
 var (
-	addFileSuffix    = flag.Bool("add_file_suffix", false, "Append the out_format to the out_file, even if there is only one out_format specified")
-	outFile          = flag.String("output_file", "", "File to which the serialized config should be written. Defaults to stdout. When multiple output formats are specified, it will append the format to the filename")
-	outFilename      = flag.String("out_filename", "", "The base name to use for writing files. Should not be used with output_file.")
-	outDir           = flag.String("out_dir", "", "The directory into which files should be written.")
-	outFormat        = flag.String("out_format", "bin", "Specifies the output formats (separated by ' '). Supports 'bin' (serialized proto), 'go' (a golang package), 'b64' (serialized proto to base 64), 'cpp' (a C++ file containing a variable with a base64-encoded serialized proto) 'dart' (a Dart library), 'json' (a JSON object), and 'rust' (a rust crate)")
-	features         = flag.String("features", "", "A comma separated list of source generator features to enable.")
-	namespace        = flag.String("namespace", "", "When using the 'cpp', 'rust', or 'go' output format, this will specify the period-separated namespace within which the config variable must be placed (this will be transformed into an underscore-separated package name for go).")
-	goPackageName    = flag.String("go_package", "", "When using the 'go' output format, this will specify the package for generated code.")
-	dartOutDir       = flag.String("dart_out_dir", "", "The directory to write dart files to (if different from out_dir)")
-	varName          = flag.String("var_name", "config", "When using the 'cpp' or 'dart' output format, this will specify the variable name to be used in the output.")
-	checkOnly        = flag.Bool("check_only", false, "Only check that the configuration is valid.")
-	allowEmptyOutput = flag.Bool("allow_empty_output", false, "Relax the requirement that the cobalt registry output not be empty.")
+	addFileSuffix     = flag.Bool("add_file_suffix", false, "Append the out_format to the out_file, even if there is only one out_format specified")
+	outFile           = flag.String("output_file", "", "File to which the serialized config should be written. Defaults to stdout. When multiple output formats are specified, it will append the format to the filename")
+	outFilename       = flag.String("out_filename", "", "The base name to use for writing files. Should not be used with output_file.")
+	outDir            = flag.String("out_dir", "", "The directory into which files should be written.")
+	outFormat         = flag.String("out_format", "bin", "Specifies the output formats (separated by ' '). Supports 'bin' (serialized proto), 'go' (a golang package), 'b64' (serialized proto to base 64), 'cpp' (a C++ file containing a variable with a base64-encoded serialized proto) 'dart' (a Dart library), 'json' (a JSON object), and 'rust' (a rust crate)")
+	features          = flag.String("features", "", "A comma separated list of source generator features to enable.")
+	namespace         = flag.String("namespace", "", "When using the 'cpp', 'rust', or 'go' output format, this will specify the period-separated namespace within which the config variable must be placed (this will be transformed into an underscore-separated package name for go).")
+	goPackageName     = flag.String("go_package", "", "When using the 'go' output format, this will specify the package for generated code.")
+	dartOutDir        = flag.String("dart_out_dir", "", "The directory to write dart files to (if different from out_dir)")
+	varName           = flag.String("var_name", "config", "When using the 'cpp' or 'dart' output format, this will specify the variable name to be used in the output.")
+	privacyParamsPath = flag.String("privacy_params_path", "", "For output formats that require an error estimate, this specifies the path to a file containing required privacy parameters.")
+	checkOnly         = flag.Bool("check_only", false, "Only check that the configuration is valid.")
+	allowEmptyOutput  = flag.Bool("allow_empty_output", false, "Relax the requirement that the cobalt registry output not be empty.")
 )
 
 // checkFlags verifies that the specified flags are compatible with each other.
@@ -83,10 +85,20 @@
 	if err := checkFlags(); err != nil {
 		return err
 	}
+
+	var errorCalc *privacy.ErrorCalculator
+	if *privacyParamsPath != "" {
+		var err error
+		errorCalc, err = privacy.NewErrorCalculatorFromPrivacyParams(*privacyParamsPath)
+		if err != nil {
+			return err
+		}
+	}
+
 	generateFilename := filenameGeneratorFromFlags()
 	features := strings.Split(*features, ",")
 	for _, format := range parseOutFormatList(*outFormat) {
-		outputFormatter, err := getOutputFormatter(format, *namespace, *goPackageName, *varName, features, *outFilename)
+		outputFormatter, err := getOutputFormatter(format, *namespace, *goPackageName, *varName, features, *outFilename, errorCalc)
 		if err != nil {
 			return err
 		}