← Professional Data Engineer: pipelines and data decisions
22 / 23 · 135 MIN

Recover reports, models and sharing

Diagnose join omissions and duplication, evaluate models against explicit inputs and recover authorized sharing paths.

1. Recover the decision supported by the report

Imagine a fictional daily fund close. Tables have been restored, the job finished and the dashboard opens again. Before declaring recovery, identify the decision the report supports: accepting positions by book, investigating differences or releasing a publication. Write down the time cut, expected population, measurement unit and consumer identity. A chart containing plausible numbers might represent the wrong day or omit precisely the trades that require intervention. Prepare a short record: input version, rate version, query version, expected result by book, permitted exceptions and acceptance owner. This record separates a source failure from an incorrect transformation. If nobody can reconstruct the expected population, validation remains incomplete even when technical checks are green. Combine engineering evidence with an independent business reference, avoiding use of the same defective query on both sides of the comparison. State how the reference was obtained and who checked its scope. In this lesson the lab evaluates only a bounded cardinality contract. It has no rate dates, close calendar, customer identification or connection to real systems. Its result helps locate errors; it does not approve a banking publication. Examples are original and fictional, with no claim to represent BNP Paribas procedures. Allow time to explain these limits to the business owner before presenting recovery status.

2. Diagnose multiplicity before aggregation

The lab receives identified trades and a currency-rate reference. Each trade must find exactly one rate. This contract differs from requiring final row count to equal initial row count: a missing match can offset a duplicate. In the fixture, the EUR trade finds two rates while the GBP trade finds none. The query produces two rows for two received trades, but loses the GBP identity and repeats EUR. Run diagnostics by identifier first. For each trade, record matches, leftRows and scaledSum. The matches field counts actual reference matches. Use missingTradeIds to locate omissions and multipliedTradeIds to locate multiplication. These lists are more useful for investigation than an isolated global indicator because they identify a trade and let the engineer trace its currency through rate preparation. Retain both the original diagnostic and the corrected result as evidence of what changed. Do not apply DISTINCT to converted amounts as an automatic repair. Two legitimate trades can have equal amounts; deleting one removes valid activity. Nor should the first rate be selected without an approved version rule. Repair reference preparation according to the actual contract and repeat diagnostics. In this exercise, duplicate rates for unused currencies do not block the report. That choice is explicit: the check measures matches required by input rather than certifying the entire reference.

3. Interpret NULL, zero and units

A trade without a rate remains in the LEFT JOIN diagnostic. Its leftRows is one, matches is zero and scaledSum is null. This distinguishes the trade’s existence from the reference’s existence. Do not immediately turn null into zero in the dashboard: that substitution would make a missing rate look like a valid conversion with no value. Send the identifier for investigation and preserve the meaning of its state rather than hiding it in presentation logic. The opposite case matters too. Two distinct trades of +100 and -100 with the same unique rate produce a zero sum. The book must remain in output with two trades. A net sum measures neither activity nor absence of movements. During production acceptance, combine counts, identities and amounts; explain which indicator supports each acceptance criterion and how discrepancies will be assigned. The lab uses integer amounts in minor units and integer rates in parts per million. scaledSum is a numerator with denominator 1000000; the program chooses no rounding policy. For example, 100 times 1200000 produces 120000000, representing 120 minor units after conceptual division. Limits of 100 trades, 100 rates, absolute amount 1000000 and rate up to 100000000 keep even the worst multiplication within the integer range used. These choices avoid confusing a join error with a numerical precision error.

4. Execute and challenge the fixture

From the project root, run python3 content/labs/pde-report-reconcile/run.py < content/labs/pde-report-reconcile/case.json. The code displayed below is the same executable file. It uses only Python and standard-library SQLite, creates an in-memory database and discards state at completion. It accepts no arbitrary SQL, database paths, credentials or cloud resource names. Input is validated before the fixed queries execute, keeping the exercise reproducible without external dependencies. Predict the result before running: inputTrades and joinedRows are both two, missingTradeIds contains missing, multipliedTradeIds contains duplicated and reconciledReport is null. unsafePreview preserves the problematic query result for comparison. Do not use it as an approved report. Copy the fixture, remove one EUR rate and add a GBP rate. Run again and confirm that each trade has one match and reconciledReport is no longer null. Next change the GBP amount to zero, remove its rate and observe that the missing match still blocks. Try two offsetting trades within one book and check activity with a zero sum. Reorder trades and rates: results should remain unchanged because diagnostics and groups are sorted. Finally supply an empty list. The contract passes without trades and returns an empty list; only business context can decide whether that absence is expected or an upstream incident.

5. Evaluate the model on the intended population

A recovered report can feed an exception classifier. Before comparing metrics, fix the model, population, cut and labels. For classification in BigQuery ML, ML.EVALUATE without an input table or query returns metrics generated during training. A successful call of that kind is not evidence that the recovered population was evaluated. Record input explicitly and retain the query selecting it, including the conditions that define which records belong to the comparison. Imagine the model was trained with label is_exception while the recovered table stores the same information in outcome. A query can adapt the name if type and meaning agree. Adding a constant label merely to satisfy schema produces a misleading evaluation. Also confirm that actual outcomes are available: a trade with no settled outcome must not receive invented ground truth to speed approval. Assign responsibility for resolving missing outcomes and document the effect of exclusions on coverage. If the model contains TRANSFORM, stored preprocessing is applied during evaluation and prediction. Inspect the new wrapper to avoid transforming the same value twice. In a guided check, document one raw input, its expected transformation and the observed prediction. A difference should be attributable to an identifiable change. Retain wrapper, model and input versions so another engineer can repeat the comparison without relying on your memory.

6. Connect model errors to a decision

A global metric does not replace a decision about tolerable errors. In a fictional example, the evaluation rule assigns cost 500 to a false negative and 5 to a false positive. Threshold A produces eight false negatives and ten false positives; B produces two and one hundred. Calculate totals first: A costs 4050 and B costs 1500. Under this rule B is preferable, although it creates more investigation work. Explicitly identify which outcome is considered positive before interpreting either error count. That conclusion has clear limits. The example does not demonstrate that the team can handle one hundred alerts, that costs reflect actual losses or that future populations will match. Take those questions to the process owner. Capacity, escalation or acceptance criteria might need adjustment. Do not turn an educational calculation into financial advice or an institution’s operational rule. Record assumptions alongside results so reviewers can challenge them. To compare thresholds for a binary BigQuery ML classifier, hold the model and labeled input constant and vary the ML.CONFUSION_MATRIX threshold. A custom threshold requires input and does not apply to multiclass classification. Retain the matrix, positive-label definition and cost rule. If population, model and threshold all change together, an apparent improvement no longer identifies its cause. Prepare the decision with reproducible evidence and an explanation understandable to whoever will handle the resulting workload.

7. Recover the consumer’s authorized path

Analytical recovery includes the access path. An authorized view can still exist after its source has changed. Draw three relationships: view authorization on the source, consumer access to the view and ability to create query jobs in the execution project. An error in one relationship is not resolved by indiscriminately granting read access to every table. First identify the permission and resource named by the error and preserve enough context to reproduce it. In a change exercise, the view moves from an old dataset to a newly recovered one. Review its projection and filters, configure required authorization on the new source and check consumer grants. A successful test through the view does not demonstrate effective restriction if the same user also has direct source access. Include direct and inherited paths in the review, recording the identity used for each acceptance artifact and the intended boundary of its access. In BigQuery sharing, revoking a subscription prevents querying the linked dataset while leaving that resource in the subscriber project. Console presence does not prove continued access. For a multi-region listing, revocation also blocks queries against linked secondary replicas. Do not use a replica as an authorization shortcut. At handover, distinguish availability, subscription and permission problems, assigning investigation to an owner able to correct each relationship.

8. Produce evidence for RUN handover

Finish the exercise with a small acceptance package. Include original input, failed diagnostics, the change made, the new result, versions used and the decision taken. For the lab, explain why global counts agreed and which identifiers were affected. Add a statement of what remains unproven: source completeness and temporal validity of rates are outside the model. This keeps the evidence useful without assigning it a broader meaning than the exercise supports. In a real project, bring data, APS, business and security owners together to review their respective criteria. Define who investigates a currency without a reference, who approves a replacement rate, who validates the population and who manages sharing authorization. A two-in-the-morning incident needs executable instructions rather than only a project presentation. RUN needs to know where evidence lives, which signals block acceptance and who receives discrepancies before close. Include an example escalation containing an identifier and the observed contract violation. Supply a concrete operational alternative too: retain the last approved publication while investigating the new set, if business and freshness requirements permit. Do not assume old data is always acceptable. Record the tolerance and decision. Summarize learning in three distinct checks: trades have the right matches, the model was evaluated against the declared population and the consumer accesses data only through approved paths.

"""Original offline exercise: reconcile join cardinality with fixed in-memory SQL."""
import json
import re
import sqlite3
import sys


def exact_keys(row, keys):
 if not isinstance(row, dict) or set(row)!= set(keys):
 raise ValueError('Unexpected object fields')


def integer(value, low, high):
 if type(value) is not int or not low <= value <= high:
 raise ValueError('Integer outside contract')


def token(value, currency=False):
 pattern = r'[A-Z]{3}' if currency else r'[A-Za-z0-9_-]{1,48}'
 if not isinstance(value, str) or re.fullmatch(pattern, value) is None:
 raise ValueError('Invalid identifier')


def reconcile(data):
 exact_keys(data, ['trades', 'rates'])
 for key in ['trades', 'rates']:
 if not isinstance(data[key], list) or len(data[key]) > 100:
 raise ValueError('Expected at most100rows per collection')
 seen = set
 for trade in data['trades']:
 exact_keys(trade, ['id', 'book', 'currency', 'amountMinor'])
 token(trade['id']); token(trade['book']); token(trade['currency'], True)
 integer(trade['amountMinor'], -1000000, 1000000)
 if trade['id'] in seen:
 raise ValueError('Duplicate trade identifier')
 seen.add(trade['id'])
 for rate in data['rates']:
 exact_keys(rate, ['currency', 'partsPerMillion'])
 token(rate['currency'], True)
 integer(rate['partsPerMillion'], 1, 100000000)
 # Even100x100matches with products of10^14 stay below signed64-bit SUM.
 db = sqlite3.connect(':memory:')
 db.row_factory = sqlite3.Row
 try:
 db.executescript('''
 CREATE TABLE trades(id TEXT PRIMARY KEY,book TEXT NOT NULL,
 currency TEXT NOT NULL,amount INTEGER NOT NULL);
 CREATE TABLE rates(currency TEXT NOT NULL,ppm INTEGER NOT NULL);
 ''')
 db.executemany('INSERT INTO trades VALUES(?,?,?,?)',
 [(t['id'],t['book'],t['currency'],t['amountMinor']) for t in data['trades']])
 db.executemany('INSERT INTO rates VALUES(?,?)',
 [(r['currency'],r['partsPerMillion']) for r in data['rates']])
 diagnostics = [dict(r) for r in db.execute('''
 SELECT t.id,COUNT(*) AS leftRows,COUNT(r.currency) AS matches,
 SUM(t.amount*r.ppm) AS scaledSum
 FROM trades t LEFT JOIN rates r ON t.currency=r.currency
 GROUP BY t.id ORDER BY t.id
 ''')]
 preview = [dict(r) for r in db.execute('''
 SELECT t.book,COUNT(*) AS joinedRows,SUM(t.amount*r.ppm) AS scaledSum
 FROM trades t INNER JOIN rates r ON t.currency=r.currency
 GROUP BY t.book ORDER BY t.book
 ''')]
 missing = [r['id'] for r in diagnostics if r['matches'] == 0]
 duplicated = [r['id'] for r in diagnostics if r['matches'] > 1]
 passed = not missing and not duplicated
 return dict(sqliteVersion=sqlite3.sqlite_version,inputTrades=len(data['trades']),
 joinedRows=sum(r['matches'] for r in diagnostics),
 diagnostics=diagnostics,missingTradeIds=missing,
 multipliedTradeIds=duplicated,unsafePreview=preview,
 cardinalityPassed=passed,reconciledReport=preview if passed else None,
 scaleDenominator=1000000,stateLifetime='one invocation; memory only',
 sourceCompletenessProven=False,rateDateValidityProven=False,
 cloudSemanticsValidated=False,productionPublicationApproved=False)
 finally:
 db.close


def unique_object(pairs):
 row = {}
 for key, value in pairs:
 if key in row:
 raise ValueError('Duplicate JSON key')
 row[key] = value
 return row


if __name__ == '__main__':
 try:
 raw = sys.stdin.buffer.read(2000001)
 if len(raw) > 2000000:
 raise ValueError('Input exceeds2MB')
 result = reconcile(json.loads(raw, object_pairs_hook=unique_object))
 print(json.dumps(result, ensure_ascii=False, sort_keys=True))
 except (ValueError, TypeError, UnicodeError, RecursionError) as exc:
 print(json.dumps({'error': str(exc)}), file=sys.stderr)
 sys.exit(2)
IN PRACTICE

Fictional close: two report rows conceal one lost trade and another multiplied by reference data.

Common pitfalls

Accepting equal counts; using DISTINCT as a universal repair; turning NULL into zero; presenting training metrics as recovered-data evaluation; confusing a visible resource with authorization.

Related topics: Cardinality and reconciliation · BigQuery ML and labels · Authorized views and subscriptions

Take this idea with you

Reconcile identities before summing, fix input before evaluating and check each authorization relationship before cutover.

Create account

Reference: Professional Data Engineer standard exam guide · Current linked standard guide (document title v4.2); edition date unconfirmed (2026-09-30 inspection)

Google Cloud is a trademark of Google LLC. bigsavant.com is an independent preparation platform and is not affiliated with, associated with, sponsored, authorised or endorsed by Google. Content and questions are original, are not official exam questions, and completing our tests does not award or guarantee any certification. Names are used only to identify the subject. All other trademarks belong to their respective owners.