Analytics Taxonomy Drift
Pricing
$100.00 / 1,000 completed reports
Analytics Taxonomy Drift
Compare supplied analytics event catalogs for added or removed events, property changes and taxonomy drift. Get one complete deterministic report from a bounded offline comparison. No live analytics access or website fetching.
Pricing
$100.00 / 1,000 completed reports
Rating
0.0
(0)
Developer
Picknotes
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
4 days ago
Last modified
Categories
Share
Compare one versioned tracking plan with one caller-aggregated observation window. Get a complete JSON report of unexpected events and properties, exact type-label mismatches, observed plan versions, and planned events unobserved in the window.
Scope and privacy
This is an aggregate-only comparison. Supply only authorized, nonpersonal, nonconfidential taxonomy labels and counts. Do not submit raw events, property values, user/device identifier values, personal or sensitive data, secrets, or confidential tracking plans. User-level joins, profiling, remote plan fetching, cross-window trends, and anomaly detection are unsupported. A name such as userId is allowed as a taxonomy label; actual identifier values are not.
The platform stores submitted input before validation. Rejection is not a privacy safeguard. Review your platform storage, retention, and access settings before submitting anything. Names are echoed into reports; the tool neither detects all personal data hidden in valid labels nor anonymizes it. Reports also contain counts, window timestamps, versions, source byte lengths, and source hashes. Keep input and output access appropriately restricted.
Price
USD 0.10 per completed report, including platform usage. One completed report compares one plan with one window; reports with drift findings cost the same. The synthetic demo follows the same paid, complete-report path and costs USD 0.10. It is not a free preview.
A receipt describes a completion-event intent; it is not evidence of payment, settlement, or an independently verified invoice. Its settlementStatus is not_verified. Check the platform's actual billing records for charges.
Input
Use JSON input with one of the following shapes. Unknown fields are rejected.
Synthetic demo
{"useDemo": true}
Do not include planJson or observationsJson in demo mode. If you include authorizedNonpersonalNonconfidentialData, it must be false in demo mode. The fixed synthetic example includes type drift, an unexpected property and event, older and unknown versions, and an unobserved planned event.
Your own aggregates
Set useDemo to false and authorizedNonpersonalNonconfidentialData to true. planJson and observationsJson must be JSON-encoded strings, not nested objects or URLs. Consent asserts that you are authorized to submit the data and that it is nonpersonal and nonconfidential.
Save this as plan.json:
{"schemaVersion":"analytics-taxonomy-plan/1","planVersion":"v1","events":[{"name":"checkout","properties":[{"name":"amount","type":"number"}]}]}
Save this as observations.json:
{"schemaVersion":"analytics-taxonomy-observations/1","window":{"start":"2026-10-01T00:00:00Z","end":"2026-10-02T00:00:00Z"},"coverage":"partial","totalEventCount":3,"groups":[{"event":"checkout","planVersion":"v1","eventCount":3,"propertyCoverage":"partial","properties":[{"name":"amount","typeCounts":[{"type":"integer","count":2},{"type":"number","count":1}]}]}]}
Create INPUT.json locally with Python 3, preserving the exact UTF-8 source strings without newline conversion:
python3 - <<'PY'import jsonfrom pathlib import Pathvalue = {"useDemo": False,"authorizedNonpersonalNonconfidentialData": True,"planJson": Path("plan.json").read_bytes().decode("utf-8"),"observationsJson": Path("observations.json").read_bytes().decode("utf-8"),}raw = json.dumps(value, ensure_ascii=True, separators=(",", ":")).encode("ascii")if len(raw) > 4_500_000:raise SystemExit("Outer input exceeds 4,500,000 bytes")with open("INPUT.json", "xb") as output:output.write(raw)PY
Submit the contents of INPUT.json as the Actor input. The example reports two integer observations as a mismatch against declared number; these labels are intentionally distinct.
Exact input contract
All fields shown in each plan, observation, group, property, and type-count object are required. Additional fields are rejected. Arrays may be empty.
- Property types are exactly
array,boolean,integer,null,number,object, orstring. These are labels, not nested values; there is no subtype, nullability, or required-property inference. - Event/property names match ASCII
[A-Za-z_][A-Za-z0-9_.:-]{0,95}. Versions match ASCII[A-Za-z0-9][A-Za-z0-9_.-]{0,63}. Matching is exact and case-sensitive; strings are not trimmed or normalized. - Group
planVersionis a version string ornull. The plan'splanVersionis a string. coverageandpropertyCoverageare exactlycomplete,partial, orunknown. These are caller assertions, not verified coverage.- Timestamps are valid Gregorian UTC seconds, exactly
YYYY-MM-DDTHH:MM:SSZ, years 0001–9999. Start must precede end. The declared window is half-open[start,end). Offsets, fractions, leap seconds, and lowercase timestamp syntax are rejected. - Use UTF-8 JSON without a BOM. Duplicate object keys, missing/unknown fields, floating-point or exponent numeric tokens, NaN/Infinity, negative integers, and
-0are rejected. Booleans are not counts. Whitespace counts toward byte limits and changes exact source hashes. - Plan event names are unique; property names are unique within each event/group; type labels are unique within each property. Observation
(event, planVersion)pairs are unique, including zero-count groups. Duplicates are rejected, never merged. totalEventCountequals the sum of every group'seventCount, including other and unknown versions. Each property's type counts sum to at most its group's count. You assert at most one type per event for that property. Different properties may overlap; their counts are not summed together against the group denominator.
How to interpret the report
Version is checked first. Null versions become unknown-version; different versions become version-mismatch, even at zero count. These groups have no property findings. Only exact matching-version groups are compared.
A zero-count matching-version group is unobserved-group, regardless of whether its name is planned. Positive matching groups are matched-event or unexpected-event. Unexpected events have no property-level findings because there is no corresponding declaration to compare.
Within a positive matched event, a positive-count undeclared property appears in unexpectedProperties. A declared property with positive counts of different type labels appears in typeMismatches, with its expectedType and only the nonmatching positive counts. Zero-only and omitted properties produce no findings. Declaring a property does not make it required.
observedVersions aggregates positive-count groups only. These are caller-supplied tracking-plan versions, not proof of deployed SDK/runtime versions. plannedUnobserved lists every plan event without a positive matching-version group. Observations under another or unknown version do not remove an event from this list. Unobserved-in-window does not prove missing instrumentation. No event-renaming inference occurs.
Complete property coverage lets you interpret omitted properties or the difference between an event count and a property's counts as absence only under your own completeness assertion. With partial/unknown coverage, absence is unknown. The report never emits missing-property violations under any coverage. Counts describe supplied aggregates, not population rates or extrapolations. Aggregates cannot prove observation timestamps, freshness, completeness, or omitted activity. No CloudEvents or JSON Schema compliance certification is provided.
Bounds
All bounds must hold together; reaching one maximum does not guarantee the other limits fit.
- Combined exact UTF-8 bytes of
planJsonandobservationsJson: 2,097,152. There is no separate 1 MiB limit per source. - Raw outer Actor input: 4,500,000 bytes, including JSON string escaping.
- Plan events: 1,000. Observation groups: 1,000.
- Properties per event or group: 100. Combined property rows across plan and observations: 10,000.
- Type-count rows per observed property: 7.
- JSON container depth per source: 8, root counted as depth 1.
- JSON nodes per source: 150,000; containers and scalar values count, object keys do not.
- Names: 96 ASCII characters. Versions: 64 ASCII characters.
- Each count and
totalEventCount: 0 through 1,000,000,000; numeric token length at most 10. - Complete native report: 2,097,152 bytes.
- Actual escaped singleton delivery POST: 4,500,000 bytes. Receipt: 2,048 bytes.
Output must fit in full: no truncation or partial-success report. Bounds failures occur before a completion-event attempt. A failed run is not evidence that no platform usage occurred. Processing is resource-limited: 448 MiB per-process address space, 30/31-second soft/hard CPU limits, a 50-second outer wall limit, and 10-second worker budgets. These are not combined-container resource or guaranteed end-to-end latency promises; OS process creation, termination, and reaping can delay return. Resource failures do not produce a usable partial report.
Output and extraction
The delivery is one raw dataset item containing reportJson, reportBytes, reportSha256, source metadata, a deliveryKey, and a receipt. Export the dataset as raw JSON, preserving all fields and the complete reportJson string. Do not use CSV, flattened fields, projections, or a shortened preview. The raw export must contain exactly one item, either as a singleton JSON array or a single JSON object.
The following standalone Python 3 script needs only the standard library and no downloaded helper. Save it as extract_report.py. It checks the envelope identifier, report size, exact UTF-8 byte count and SHA-256, and basic report identity before writing a new private file. It does not modify or reserialize report bytes.
import hashlibimport jsonimport osimport reimport statimport sysMAX_EXPORT = 4_500_000MAX_REPORT = 2_097_152def unique(pairs):result = {}for key, value in pairs:if key in result:raise ValueError("duplicate JSON key")result[key] = valuereturn resultdef reject_constant(value):raise ValueError("invalid JSON constant")def decode(raw):text = raw.decode("utf-8")if text.startswith("\ufeff"):raise ValueError("BOM is not supported")return json.loads(text, object_pairs_hook=unique,parse_constant=reject_constant)def main():if len(sys.argv) != 3:raise ValueError("usage: extract_report.py raw-dataset.json NEW-report.json")# Linux/POSIX: reject final-component symlinks and nonregular input.# Use trusted, private parent directories; this is not a filesystem sandbox.fd = os.open(sys.argv[1], os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK)with os.fdopen(fd, "rb") as source:info = os.fstat(source.fileno())if not stat.S_ISREG(info.st_mode) or info.st_size > MAX_EXPORT:raise ValueError("invalid or oversized export")raw = source.read(MAX_EXPORT + 1)if len(raw) > MAX_EXPORT:raise ValueError("oversized export")item = decode(raw)if type(item) is list:if len(item) != 1:raise ValueError("expected exactly one dataset item")item = item[0]if type(item) is not dict:raise ValueError("expected an object")if (item.get("schemaVersion") != "bounded-report-delivery/1"or item.get("contractId") != "analytics-taxonomy-drift"or item.get("contractVersion") != "0.1"):raise ValueError("unexpected delivery identity")if type(item.get("reportJson")) is not str:raise ValueError("reportJson must be a string")report = item["reportJson"].encode("utf-8")size = item.get("reportBytes")digest = item.get("reportSha256")if (type(size) is not int or not 1 <= size <= MAX_REPORTor len(report) != size):raise ValueError("report byte count mismatch")if (type(digest) is not stror re.fullmatch(r"[0-9a-f]{64}", digest) is Noneor hashlib.sha256(report).hexdigest() != digest):raise ValueError("report SHA-256 mismatch")parsed = decode(report)if (type(parsed) is not dictor parsed.get("schemaVersion") != "analytics-taxonomy-drift-report/1"or parsed.get("profile") != "owned-aggregate-taxonomy/1"or parsed.get("complete") is not True):raise ValueError("unexpected report identity")# Exclusive creation: never overwrite an existing path.fd = os.open(sys.argv[2], os.O_WRONLY | os.O_CREAT | os.O_EXCL| os.O_NOFOLLOW, 0o600)with os.fdopen(fd, "wb") as output:if output.write(report) != len(report):raise OSError("short write")output.flush()os.fsync(output.fileno())if __name__ == "__main__":try:main()except Exception:print("Extraction refused or incomplete. No existing file was overwritten. ""A newly created file may be incomplete; do not use it.", file=sys.stderr)sys.exit(2)
Run it on Linux/POSIX from a trusted private directory:
python3 -I extract_report.py raw-dataset.json NEW-report.json
Exit 0 means extraction succeeded; exit 2 means refusal or incomplete writing. The destination must not exist. A write failure can leave a newly created incomplete file, which must not be used. Pretty-printed exports count against the export byte limit; use a compact raw JSON export if necessary, without changing the reportJson string.
What these checks do and do not prove
This recipe detects a mismatch between the exported report bytes and the envelope's length/hash. It is not the product's full report-only consistency validator and does not reconstruct original-source eligibility. A malicious party can replace both a report and its declared hash. Hashes, receipt fields, and a delivery key are not signatures, sender authentication, or settlement evidence. Obtain the export through your trusted platform account and verify the intended run separately.
Full original-source eligibility requires the exact original plan and observation bytes and the source-based validator, which independently reconstructs the complete expected report. Source hashes alone do not supply those originals. Even a full report-only validator cannot recover omitted plan/property declarations to prove every original comparison. Keep your source files if you need that verification. No extraction check establishes the truth of caller aggregates or coverage assertions.
Delivery uncertainty
A completion append is attempted at most once per invocation. Delivery is confirmed only by exact readback of the complete singleton item, including after an ambiguous acknowledgement. If the append may have happened but readback cannot establish it, the result remains uncertain and may have been billed. Do not automatically repost or rerun an uncertain delivery: check the existing run's dataset and billing records first to avoid a duplicate report or charge. A successful receipt/readback still does not prove settlement.