@unified-product-graph/core 0.11.0 → 0.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,30 @@ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
  ---
9
9
 
10
+ ## [0.11.2] - 2026-06-14
11
+
12
+ **Two analysis reads over the classification layer: compare two rivals, and digest a property's distribution.** The competitive tier could render the landscape (`get_portfolio_tree`), audit its completeness (`audit_property_coverage`), and diff its history (`diff_classification`), but two by-hand motions from the backfill stayed manual: comparing two competitors axis-by-axis, and counting a property's distribution over the `jq` dump. This release lands both as read tools (Data's tooling-gaps brief #5/#6). Current-state reads, no history substrate, no schema or data migration. New tools are a patch under spec policy.
13
+
14
+ ### Added
15
+ - **`compare_classifications` (new tool, 129 of 130; local-only).** `compare_classifications({ a, b, axis? })` joins two classified nodes axis-by-axis: where they sit at the same value (`agree`), at different values (`diverge`), or where only one has been graded (`a_only` / `b_only`). Divergences are ordered first (the actionable rows). It reuses the same per-node profile assembly as `get_portfolio_tree` competitor_profile, so axis / value / confidence resolution is identical. This is the bridge from the classification layer to the parity layer: `create_parity_edge` is the writer; this derives which axes warrant one.
16
+ - **`aggregate_edge_properties` (new tool, 130 of 130; local-only).** `aggregate_edge_properties({ edge_type, group_by?, property? })` returns the distribution of one edge property across every portfolio cross-edge of a type — overall, or grouped by `axis`, `competitor`, or `value`. `property` defaults to `confidence`, and an assessment-object property buckets by its label, so "165 Confident / 53 Some evidence, the uncertain ones cluster on `ext_api_sdk`" is one call instead of a count over a dump. The digest of the property layer.
17
+
18
+ ---
19
+
20
+ ## [0.11.1] - 2026-06-14
21
+
22
+ **Bug fix: the typed classification writer misrouted competitor sources, duplicated instead of upserting, and disagreed with the confidence scale.** The generic cross-edge writers got their in-place upsert in 0.10.6; the typed convenience writer `create_classification_edge` never inherited it, and on a qualified `{product}/{node}` competitor source it wrote the wrong edge type, created a duplicate, and expanded `high` to a different `confidence_5` value than the rest of the graph. Three defects in one call (Data's dogfood brief), all on a path the generic writers already do correctly. No data migration.
23
+
24
+ ### Fixed
25
+ - **`create_classification_edge` now upserts instead of duplicating.** Root cause was the misroute below: a qualified competitor source was mis-typed as the polymorphic `node_classified_as_classification_value`, so the `(source, target, type)` dedup never matched the existing `competitor_…` edge and a second edge was created. Correct routing makes the write land on the existing cell in place (`status: "updated"`).
26
+ - **Qualified-source type routing.** A `{product}/{node}` source whose node is a `competitor` now resolves to `competitor` — via the owning product file, and failing that the portfolio's `instance_of` index (which maps `{pid}/{nid}` to its canonical type without a local product file) — and writes `competitor_classified_as_classification_value`. Bare local sources keep the polymorphic type.
27
+ - **Confidence-scale agreement.** `high` now expands to `confidence_5` value **4** (canonical label `Confident`), not 5 — matching the generic writers and the existing classify-edge population, so two writers can no longer populate the same axis with different numbers for the same word.
28
+
29
+ ### Added
30
+ - **Pinned friendly confidence mapping on the scale (`confidence_5.friendly_aliases = { low: 2, medium: 3, high: 4 }`).** The single, introspectable source of truth — surfaced via `get_scale` — that `create_classification_edge` (and any future friendly-confidence writer) resolves through `friendlyToAssessment(scaleId, word)`. The expansion carries the canonical point label (`Confident`), not the input word.
31
+
32
+ ---
33
+
10
34
  ## [0.11.0] - 2026-06-14
11
35
 
12
36
  **Self-documenting competitive history: a competitor's classification change records itself.** The 0.10.x tier classifies competitors against axes and values and carries `confidence` / `assessed_on`, but a re-assessment overwrites the prior value — there was no way to ask *what moved*. This release makes the classification landscape remember its own changes. A minor bump: it widens the `competitor_signal.signal_type` enum (union widening), which spec policy treats as a minor; additive, so old graphs still parse.
package/dist/index.cjs CHANGED
@@ -119,6 +119,7 @@ __export(index_exports, {
119
119
  evaluateAntiPatterns: () => evaluateAntiPatterns,
120
120
  findInvalidStatusMigrationTargets: () => findInvalidStatusMigrationTargets,
121
121
  formatUpgText: () => formatUpgText,
122
+ friendlyToAssessment: () => friendlyToAssessment,
122
123
  generateSlug: () => generateSlug,
123
124
  getAnchorEntity: () => getAnchorEntity,
124
125
  getAntiPatternById: () => getAntiPatternById,
@@ -6801,7 +6802,11 @@ var UPG_SCALES = {
6801
6802
  { value: 3, label: "Some evidence", description: "A few data points" },
6802
6803
  { value: 4, label: "Confident", description: "Multiple data sources" },
6803
6804
  { value: 5, label: "Data-backed", description: "Strong quantitative evidence" }
6804
- ]
6805
+ ],
6806
+ // The pinned friendly mapping (0.11.1). `high` is Confident (4), not
6807
+ // Data-backed (5) — 5 is reserved for genuinely quantified claims. Matches
6808
+ // the existing classify-edge population, so nothing needs re-stamping.
6809
+ friendly_aliases: { low: 2, medium: 3, high: 4 }
6805
6810
  },
6806
6811
  // ── Effort ─────────────────────────────────────────────────────────────────
6807
6812
  effort_5: {
@@ -6823,6 +6828,13 @@ var UPG_SCALES = {
6823
6828
  function getScale(scaleId) {
6824
6829
  return UPG_SCALES[scaleId];
6825
6830
  }
6831
+ function friendlyToAssessment(scaleId, word) {
6832
+ const scale = UPG_SCALES[scaleId];
6833
+ const value = scale?.friendly_aliases?.[word];
6834
+ if (value === void 0) return null;
6835
+ const point = scale.points.find((p) => p.value === value);
6836
+ return { value, label: point?.label ?? word, scale_id: scaleId };
6837
+ }
6826
6838
  var PROPERTY_SCALE_MAP = {
6827
6839
  // ── Reach ────────────────────────────────────────────────────────────────
6828
6840
  reach: "reach_5",
@@ -28755,7 +28767,7 @@ function isCanonical(text) {
28755
28767
  }
28756
28768
 
28757
28769
  // src/index.ts
28758
- var UPG_VERSION = "0.11.0";
28770
+ var UPG_VERSION = "0.11.2";
28759
28771
  var UPG_FORMAT_VERSION = "0.4.0";
28760
28772
  var MARKDOWN_FORMAT_VERSION = "0.1";
28761
28773
  var UPG_TYPES = getTypes();
@@ -28919,6 +28931,7 @@ var UPG_META_COUNT = UPG_ENTITY_META.length;
28919
28931
  evaluateAntiPatterns,
28920
28932
  findInvalidStatusMigrationTargets,
28921
28933
  formatUpgText,
28934
+ friendlyToAssessment,
28922
28935
  generateSlug,
28923
28936
  getAnchorEntity,
28924
28937
  getAntiPatternById,