industry-slice-core 0.1.2__tar.gz

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.
Files changed (52) hide show
  1. industry_slice_core-0.1.2/PKG-INFO +8 -0
  2. industry_slice_core-0.1.2/README.md +43 -0
  3. industry_slice_core-0.1.2/industry_slice_core/__init__.py +1 -0
  4. industry_slice_core-0.1.2/industry_slice_core/_contract.py +7 -0
  5. industry_slice_core-0.1.2/industry_slice_core/artifact_data.py +15 -0
  6. industry_slice_core-0.1.2/industry_slice_core/comps_template.py +126 -0
  7. industry_slice_core-0.1.2/industry_slice_core/comps_template_loader.py +69 -0
  8. industry_slice_core-0.1.2/industry_slice_core/data/comps_templates/industry_comps_generic_v1.fixture.csv +20 -0
  9. industry_slice_core-0.1.2/industry_slice_core/data/comps_templates/industry_comps_generic_v1.yaml +264 -0
  10. industry_slice_core-0.1.2/industry_slice_core/data/comps_templates/operating_comps_grocers_v1.fixture.csv +15 -0
  11. industry_slice_core-0.1.2/industry_slice_core/data/comps_templates/operating_comps_grocers_v1.yaml +197 -0
  12. industry_slice_core-0.1.2/industry_slice_core/data/comps_templates/operating_comps_hr_payroll_v1.fixture.csv +12 -0
  13. industry_slice_core-0.1.2/industry_slice_core/data/comps_templates/operating_comps_hr_payroll_v1.yaml +161 -0
  14. industry_slice_core-0.1.2/industry_slice_core/data/industry_kpis/caption_map.yaml +64 -0
  15. industry_slice_core-0.1.2/industry_slice_core/data/industry_kpis/grocers.yaml +139 -0
  16. industry_slice_core-0.1.2/industry_slice_core/data/industry_kpis/hr_payroll.yaml +59 -0
  17. industry_slice_core-0.1.2/industry_slice_core/data/industry_taxonomy.yaml +15 -0
  18. industry_slice_core-0.1.2/industry_slice_core/enum_canonicalizers.py +168 -0
  19. industry_slice_core-0.1.2/industry_slice_core/industry_resolver.py +72 -0
  20. industry_slice_core-0.1.2/industry_slice_core/industry_types.py +59 -0
  21. industry_slice_core-0.1.2/industry_slice_core/kpi_registry.py +104 -0
  22. industry_slice_core-0.1.2/industry_slice_core/kpi_registry_loader.py +22 -0
  23. industry_slice_core-0.1.2/industry_slice_core/manifest_bindings.json +27 -0
  24. industry_slice_core-0.1.2/industry_slice_core/manifest_bindings.py +113 -0
  25. industry_slice_core-0.1.2/industry_slice_core/metric_registry.json +97 -0
  26. industry_slice_core-0.1.2/industry_slice_core/metric_registry.py +103 -0
  27. industry_slice_core-0.1.2/industry_slice_core/numeric.py +54 -0
  28. industry_slice_core-0.1.2/industry_slice_core/profile_normalization.py +168 -0
  29. industry_slice_core-0.1.2/industry_slice_core/source_registry.py +256 -0
  30. industry_slice_core-0.1.2/industry_slice_core/strategy_categories/__init__.py +13 -0
  31. industry_slice_core-0.1.2/industry_slice_core/strategy_categories/defaults.yaml +24 -0
  32. industry_slice_core-0.1.2/industry_slice_core/strategy_categories/registry.py +159 -0
  33. industry_slice_core-0.1.2/industry_slice_core/thesis_shared_industry.py +174 -0
  34. industry_slice_core-0.1.2/industry_slice_core/thesis_shared_slice_helpers.py +165 -0
  35. industry_slice_core-0.1.2/industry_slice_core/thesis_shared_sources.py +457 -0
  36. industry_slice_core-0.1.2/industry_slice_core.egg-info/PKG-INFO +8 -0
  37. industry_slice_core-0.1.2/industry_slice_core.egg-info/SOURCES.txt +50 -0
  38. industry_slice_core-0.1.2/industry_slice_core.egg-info/dependency_links.txt +1 -0
  39. industry_slice_core-0.1.2/industry_slice_core.egg-info/requires.txt +3 -0
  40. industry_slice_core-0.1.2/industry_slice_core.egg-info/top_level.txt +1 -0
  41. industry_slice_core-0.1.2/pyproject.toml +17 -0
  42. industry_slice_core-0.1.2/setup.cfg +4 -0
  43. industry_slice_core-0.1.2/tests/test_artifact_loading.py +66 -0
  44. industry_slice_core-0.1.2/tests/test_comps_template_operating_comps.py +133 -0
  45. industry_slice_core-0.1.2/tests/test_industry_resolver.py +79 -0
  46. industry_slice_core-0.1.2/tests/test_industry_slice_imports.py +43 -0
  47. industry_slice_core-0.1.2/tests/test_kpi_registry.py +156 -0
  48. industry_slice_core-0.1.2/tests/test_numeric.py +56 -0
  49. industry_slice_core-0.1.2/tests/test_operating_comps_hr_payroll_fixture.py +61 -0
  50. industry_slice_core-0.1.2/tests/test_operating_comps_hr_payroll_yaml.py +63 -0
  51. industry_slice_core-0.1.2/tests/test_source_registry.py +261 -0
  52. industry_slice_core-0.1.2/tests/test_thesis_shared_sources.py +22 -0
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: industry-slice-core
3
+ Version: 0.1.2
4
+ Summary: Shared industry, comps, source and KPI contracts
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: pydantic<3,>=2.10
7
+ Requires-Dist: PyYAML<7,>=6.0
8
+ Requires-Dist: value-semantics-core==2.1.0
@@ -0,0 +1,43 @@
1
+ # industry-slice-core
2
+
3
+ Engine-free industry, comps, source and KPI contracts, plus the static data they
4
+ read. Pure types and loaders: no provider calls, no model engine, no publication
5
+ of accepted artifacts. The add-in gateway and the Risk service install the same
6
+ version so both sides parse the same payloads.
7
+
8
+ Tests: `python3 -m pytest packages/industry-slice-core/tests -q`
9
+
10
+ ## Public surface
11
+
12
+ | Module | Owns |
13
+ |---|---|
14
+ | `industry_types` | `CompanyProfile`, `Unit`, `DriverCategory` — plain company and assumption types shared with the compiler |
15
+ | `industry_resolver` | `resolve_industry_key` over `data/industry_taxonomy.yaml` |
16
+ | `kpi_registry`, `kpi_registry_loader` | `KPI`/`KPIRegistry` types and `load_kpi_registry` per industry key |
17
+ | `comps_template`, `comps_template_loader` | Comps manifest types, manifest/fixture loading |
18
+ | `metric_registry` | Canonical provider-neutral `metric_id` vocabulary; `require_registered_metric_id` raises `UnknownMetricIdError` |
19
+ | `source_registry` | Identity/excerpt hashing and `register_source` for cited evidence |
20
+ | `thesis_shared_industry`, `thesis_shared_sources`, `thesis_shared_slice_helpers` | Shared landscape, comparison, snapshot and source-record shapes |
21
+ | `numeric`, `enum_canonicalizers`, `profile_normalization` | Sign/scale parsing, enum canonicalization, FMP record to provider-port values |
22
+ | `strategy_categories` | Strategy-category registry and `validate_strategy_category_id` |
23
+ | `manifest_bindings` | FMP adapter configuration for legacy comps-manifest bindings |
24
+ | `artifact_data` | `artifact_path` — an accepted runtime artifact wins over the bundled default |
25
+
26
+ Contract models derive from `_contract._ContractModel`: `extra="forbid"` and
27
+ whitespace-stripped strings, so an unknown field is a validation error on both
28
+ sides of the wire rather than a silently dropped value.
29
+
30
+ ## Static data and runtime overrides
31
+
32
+ `industry_slice_core/data/` holds the bundled taxonomy, per-industry KPI files,
33
+ and comps manifests with their fixture rows. When `INDUSTRY_ARTIFACTS_DIR` is
34
+ set, `artifact_path` prefers `<dir>/industry_kpis/<file>` and
35
+ `<dir>/comps_templates/<file>` when that file exists and falls back to the
36
+ bundled copy otherwise, so a reviewed replacement is picked up on the next
37
+ request without a restart. This package reads that directory; it never writes to
38
+ it and never decides what is accepted.
39
+
40
+ In the Hank monorepo, `docs/reference/financial-modeling-studio.md` ("Industry
41
+ configuration") documents the consumer contract — which loader reads which file,
42
+ who owns `INDUSTRY_ARTIFACTS_DIR`, and how a new industry is staged for review.
43
+ That reference is outside this package; `packages/README.md` is the tree map.
@@ -0,0 +1 @@
1
+ """Engine-free industry, comps, source and KPI contracts."""
@@ -0,0 +1,7 @@
1
+ from pydantic import BaseModel, ConfigDict
2
+
3
+
4
+ class _ContractModel(BaseModel):
5
+ model_config = ConfigDict(
6
+ extra="forbid", str_strip_whitespace=True, populate_by_name=True
7
+ )
@@ -0,0 +1,15 @@
1
+ """Read accepted runtime industry artifacts ahead of bundled defaults."""
2
+
3
+ from importlib.resources import files
4
+ from importlib.resources.abc import Traversable
5
+ import os
6
+ from pathlib import Path
7
+
8
+
9
+ def artifact_path(directory: str, filename: str) -> Traversable:
10
+ root = os.environ.get("INDUSTRY_ARTIFACTS_DIR")
11
+ if root:
12
+ runtime_path = Path(root).expanduser() / directory / filename
13
+ if runtime_path.is_file():
14
+ return runtime_path
15
+ return files("industry_slice_core").joinpath("data", directory, filename)
@@ -0,0 +1,126 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Literal
4
+
5
+ from pydantic import Field, field_validator, model_validator
6
+
7
+ from value_semantics_core.numeric_display import NumericDisplaySpec
8
+ from ._contract import _ContractModel
9
+ from industry_slice_core.metric_registry import require_registered_metric_id
10
+
11
+
12
+ class CompsManifestSourceBinding(_ContractModel):
13
+ kind: Literal["concept", "metric", "transcript_kpi", "derived", "kpi"]
14
+ concept_id: str | None = None
15
+ metric_id: str | None = None
16
+ kpi_key: str | None = None
17
+ derived_formula: str | None = None
18
+
19
+ @model_validator(mode="after")
20
+ def _validate_binding_identity(self) -> "CompsManifestSourceBinding":
21
+ if self.kind == "concept" and not self.concept_id:
22
+ raise ValueError("concept bindings require concept_id")
23
+ if self.kind == "metric" and not self.metric_id:
24
+ raise ValueError("metric bindings require metric_id")
25
+ if self.kind == "metric":
26
+ self.metric_id = require_registered_metric_id(
27
+ self.metric_id,
28
+ entry_point="comps-manifest metric",
29
+ )
30
+ if self.kind != "concept" and self.concept_id is not None:
31
+ raise ValueError("concept_id is only valid for concept bindings")
32
+ if self.kind != "metric" and self.metric_id is not None:
33
+ raise ValueError("metric_id is only valid for metric bindings")
34
+ return self
35
+
36
+
37
+ class CompsManifestMetric(_ContractModel):
38
+ key: str = Field(min_length=1)
39
+ label: str = Field(min_length=1)
40
+ display: NumericDisplaySpec
41
+ order: int = Field(ge=1)
42
+ aggregation: Literal["median", "mean", "weighted"] = "median"
43
+ null_policy: Literal["skip", "zero", "fail"] = "skip"
44
+ source: CompsManifestSourceBinding
45
+
46
+
47
+ class CompsManifestSection(_ContractModel):
48
+ name: str = Field(min_length=1)
49
+ order: int = Field(ge=1)
50
+ metrics: list[CompsManifestMetric]
51
+
52
+ @field_validator("metrics")
53
+ @classmethod
54
+ def _reject_duplicate_metric_keys(
55
+ cls, value: list[CompsManifestMetric]
56
+ ) -> list[CompsManifestMetric]:
57
+ keys = [metric.key for metric in value]
58
+ duplicates = sorted({key for key in keys if keys.count(key) > 1})
59
+ if duplicates:
60
+ raise ValueError(f"duplicate metric keys: {duplicates}")
61
+ orders = [metric.order for metric in value]
62
+ duplicate_orders = sorted({order for order in orders if orders.count(order) > 1})
63
+ if duplicate_orders:
64
+ raise ValueError(f"duplicate metric orders: {duplicate_orders}")
65
+ return value
66
+
67
+
68
+ class CompsTemplateManifest(_ContractModel):
69
+ template_id: str = Field(min_length=1)
70
+ template_kind: Literal["industry_comps", "operating_comps"]
71
+ industry_key: str | None = None
72
+ source_sheet_key: str | None = Field(
73
+ default=None,
74
+ description="Comps sheet registry key for manifests bound to reusable sheets.",
75
+ )
76
+ source_gsheet_id: str | None = Field(
77
+ default=None,
78
+ description=(
79
+ "Deprecated legacy direct Google Sheet id; accepted for backcompat."
80
+ ),
81
+ )
82
+ source_gsheet_version: str | None = Field(
83
+ default=None,
84
+ description="Deprecated legacy/source version stamp; accepted for backcompat.",
85
+ )
86
+ years: list[int] | None = None
87
+ years_back: int | None = None
88
+ sections: list[CompsManifestSection]
89
+ needs_re_emission: bool = Field(default=False, exclude=True)
90
+
91
+ @model_validator(mode="after")
92
+ def _validate_manifest(self) -> "CompsTemplateManifest":
93
+ if self.source_sheet_key is not None and self.source_gsheet_id is not None:
94
+ raise ValueError(
95
+ "source_sheet_key and source_gsheet_id are mutually exclusive when non-null"
96
+ )
97
+ if self.template_kind == "operating_comps" and not self.industry_key:
98
+ raise ValueError("industry_key is required for operating_comps templates")
99
+ if self.template_kind == "operating_comps":
100
+ has_years = self.years is not None
101
+ has_years_back = self.years_back is not None
102
+ if has_years == has_years_back:
103
+ raise ValueError(
104
+ "operating_comps templates require exactly one of years or years_back"
105
+ )
106
+ section_names = [section.name for section in self.sections]
107
+ duplicate_names = sorted(
108
+ {name for name in section_names if section_names.count(name) > 1}
109
+ )
110
+ if duplicate_names:
111
+ raise ValueError(f"duplicate section names: {duplicate_names}")
112
+ section_orders = [section.order for section in self.sections]
113
+ duplicate_orders = sorted(
114
+ {order for order in section_orders if section_orders.count(order) > 1}
115
+ )
116
+ if duplicate_orders:
117
+ raise ValueError(f"duplicate section orders: {duplicate_orders}")
118
+ return self
119
+
120
+
121
+ __all__ = [
122
+ "CompsManifestMetric",
123
+ "CompsManifestSection",
124
+ "CompsManifestSourceBinding",
125
+ "CompsTemplateManifest",
126
+ ]
@@ -0,0 +1,69 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from importlib.resources.abc import Traversable
5
+ from typing import Any
6
+
7
+ import yaml
8
+
9
+ from industry_slice_core.comps_template import CompsTemplateManifest
10
+ from industry_slice_core.manifest_bindings import (
11
+ translate_legacy_manifest_payload,
12
+ )
13
+
14
+
15
+ from .artifact_data import artifact_path
16
+
17
+
18
+ def load_comps_template_manifest(
19
+ template_id: str,
20
+ *,
21
+ template_root: Path | None = None,
22
+ ) -> CompsTemplateManifest:
23
+ normalized_id = str(template_id or "").strip()
24
+ if not normalized_id:
25
+ raise ValueError("template_id is required")
26
+ path = (
27
+ template_root / f"{normalized_id}.yaml"
28
+ if template_root is not None
29
+ else artifact_path("comps_templates", f"{normalized_id}.yaml")
30
+ )
31
+ return load_comps_template_manifest_path(path)
32
+
33
+
34
+ def load_comps_template_manifest_path(path: Path | Traversable) -> CompsTemplateManifest:
35
+ payload = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
36
+ if not isinstance(payload, dict):
37
+ raise ValueError(f"comps template manifest must be a mapping: {path}")
38
+ translated, _changed = translate_legacy_manifest_payload(payload)
39
+ return CompsTemplateManifest.model_validate(translated)
40
+
41
+
42
+ def manifest_fixture_rows(manifest: CompsTemplateManifest) -> list[dict[str, Any]]:
43
+ rows: list[dict[str, Any]] = []
44
+ for section in sorted(manifest.sections, key=lambda item: item.order):
45
+ for metric in sorted(section.metrics, key=lambda item: item.order):
46
+ rows.append(
47
+ {
48
+ "section_order": section.order,
49
+ "section_name": section.name,
50
+ "metric_order": metric.order,
51
+ "metric_key": metric.key,
52
+ "label": metric.label,
53
+ "aggregation": metric.aggregation,
54
+ "null_policy": metric.null_policy,
55
+ "source_kind": metric.source.kind,
56
+ "concept_id": metric.source.concept_id or "",
57
+ "metric_id": metric.source.metric_id or "",
58
+ "kpi_key": metric.source.kpi_key or "",
59
+ "derived_formula": metric.source.derived_formula or "",
60
+ }
61
+ )
62
+ return rows
63
+
64
+
65
+ __all__ = [
66
+ "load_comps_template_manifest",
67
+ "load_comps_template_manifest_path",
68
+ "manifest_fixture_rows",
69
+ ]
@@ -0,0 +1,20 @@
1
+ section_order,section_name,metric_order,metric_key,label,aggregation,null_policy,source_kind,concept_id,metric_id,kpi_key,derived_formula
2
+ 1,Performance (LTM),1,revenue_ltm,Revenues ($M),median,skip,concept,revenue,,,
3
+ 1,Performance (LTM),2,ebitda_ltm,EBITDA ($M),median,skip,concept,ebitda,,,
4
+ 1,Performance (LTM),3,ebit_ltm,EBIT ($M),median,skip,metric,,ebit,,
5
+ 1,Performance (LTM),4,dna_ltm,D&A ($M),median,skip,concept,depreciation_amortization,,,
6
+ 1,Performance (LTM),5,revenue_growth_ltm,Revenue Growth,median,skip,metric,,revenue_growth,,
7
+ 1,Performance (LTM),6,roe_ltm,ROE,median,skip,metric,,return_on_equity,,
8
+ 2,Valuation,1,ev_revenue_ltm,EV / Revenue,median,skip,metric,,ev_revenue,,
9
+ 2,Valuation,2,ev_ebitda_ltm,EV / EBITDA,median,skip,metric,,ev_ebitda,,
10
+ 2,Valuation,3,net_debt_ltm,Net Debt ($M),median,skip,metric,,net_debt,,
11
+ 2,Valuation,4,cash_ltm,Cash ($M),median,skip,concept,cash_and_equivalents,,,
12
+ 3,EPS forward,1,eps_fy1,FY1 EPS,median,skip,metric,,eps_avg,,
13
+ 3,EPS forward,2,eps_fy2,FY2 EPS,median,skip,metric,,eps_avg,,
14
+ 3,EPS forward,3,eps_fy3,FY3 EPS,median,skip,metric,,eps_avg,,
15
+ 3,EPS forward,4,eps_2y_cagr,2Y EPS CAGR,median,skip,derived,,,,(eps_fy3 / eps_fy1) ** 0.5 - 1
16
+ 4,P/E multiples,1,pe_ltm,P / E,median,skip,metric,,reported_pe_ratio,,
17
+ 4,P/E multiples,2,pe_fy1,FY1 P / E,median,skip,derived,,,,price / eps_fy1
18
+ 5,Dividend,1,dividend_yield,Dividend Yield,median,skip,metric,,dividend_yield,,
19
+ 5,Dividend,2,dividend_per_share,Dividend / Share,median,skip,metric,,dividend_per_share,,
20
+ 5,Dividend,3,dividends_paid_ltm,Dividends Paid ($M),median,skip,concept,dividends_paid,,,
@@ -0,0 +1,264 @@
1
+ template_id: "industry_comps_generic_v1"
2
+ template_kind: "industry_comps"
3
+ industry_key: null
4
+ source_gsheet_id: null
5
+ source_gsheet_version: "track0_fixture_2026_05_07"
6
+ sections:
7
+ - name: "Performance (LTM)"
8
+ order: 1
9
+ metrics:
10
+ - key: "revenue_ltm"
11
+ label: "Revenues ($M)"
12
+ display:
13
+ numeric_kind: currency
14
+ scale: millions
15
+ currency: USD
16
+ precision: 1
17
+ order: 1
18
+ aggregation: "median"
19
+ null_policy: "skip"
20
+ source:
21
+ kind: "concept"
22
+ concept_id: "revenue"
23
+ - key: "ebitda_ltm"
24
+ label: "EBITDA ($M)"
25
+ display:
26
+ numeric_kind: currency
27
+ scale: millions
28
+ currency: USD
29
+ precision: 1
30
+ order: 2
31
+ aggregation: "median"
32
+ null_policy: "skip"
33
+ source:
34
+ kind: "concept"
35
+ concept_id: "ebitda"
36
+ - key: "ebit_ltm"
37
+ label: "EBIT ($M)"
38
+ display:
39
+ numeric_kind: currency
40
+ scale: millions
41
+ currency: USD
42
+ precision: 1
43
+ order: 3
44
+ aggregation: "median"
45
+ null_policy: "skip"
46
+ source:
47
+ kind: "metric"
48
+ metric_id: "ebit"
49
+ - key: "dna_ltm"
50
+ label: "D&A ($M)"
51
+ display:
52
+ numeric_kind: currency
53
+ scale: millions
54
+ currency: USD
55
+ precision: 1
56
+ order: 4
57
+ aggregation: "median"
58
+ null_policy: "skip"
59
+ source:
60
+ kind: "concept"
61
+ concept_id: "depreciation_amortization"
62
+ - key: "revenue_growth_ltm"
63
+ label: "Revenue Growth"
64
+ # arrives fraction
65
+ display:
66
+ numeric_kind: percentage
67
+ scale: unit
68
+ precision: 1
69
+ order: 5
70
+ aggregation: "median"
71
+ null_policy: "skip" # revenueGrowthTTM unavailable; data gap
72
+ source:
73
+ kind: "metric"
74
+ metric_id: "revenue_growth"
75
+ - key: "roe_ltm"
76
+ label: "ROE"
77
+ # arrives fraction
78
+ display:
79
+ numeric_kind: percentage
80
+ scale: unit
81
+ precision: 1
82
+ order: 6
83
+ aggregation: "median"
84
+ null_policy: "skip"
85
+ source:
86
+ kind: "metric"
87
+ metric_id: "return_on_equity"
88
+ - name: "Valuation"
89
+ order: 2
90
+ metrics:
91
+ - key: "ev_revenue_ltm"
92
+ label: "EV / Revenue"
93
+ display:
94
+ numeric_kind: multiple
95
+ scale: unit
96
+ precision: 1
97
+ order: 1
98
+ aggregation: "median"
99
+ null_policy: "skip"
100
+ source:
101
+ kind: "metric"
102
+ metric_id: "ev_revenue"
103
+ - key: "ev_ebitda_ltm"
104
+ label: "EV / EBITDA"
105
+ display:
106
+ numeric_kind: multiple
107
+ scale: unit
108
+ precision: 1
109
+ order: 2
110
+ aggregation: "median"
111
+ null_policy: "skip"
112
+ source:
113
+ kind: "metric"
114
+ metric_id: "ev_ebitda"
115
+ - key: "net_debt_ltm"
116
+ label: "Net Debt ($M)"
117
+ display:
118
+ numeric_kind: currency
119
+ scale: millions
120
+ currency: USD
121
+ precision: 1
122
+ order: 3
123
+ aggregation: "median"
124
+ null_policy: "skip"
125
+ source:
126
+ kind: "metric"
127
+ metric_id: "net_debt"
128
+ - key: "cash_ltm"
129
+ label: "Cash ($M)"
130
+ display:
131
+ numeric_kind: currency
132
+ scale: millions
133
+ currency: USD
134
+ precision: 1
135
+ order: 4
136
+ aggregation: "median"
137
+ null_policy: "skip"
138
+ source:
139
+ kind: "concept"
140
+ concept_id: "cash_and_equivalents"
141
+ - name: "EPS forward"
142
+ order: 3
143
+ metrics:
144
+ - key: "eps_fy1"
145
+ label: "FY1 EPS"
146
+ display:
147
+ numeric_kind: per_share
148
+ scale: unit
149
+ currency: USD
150
+ precision: 1
151
+ order: 1
152
+ aggregation: "median"
153
+ null_policy: "skip"
154
+ source:
155
+ kind: "metric"
156
+ metric_id: "eps_avg"
157
+ - key: "eps_fy2"
158
+ label: "FY2 EPS"
159
+ display:
160
+ numeric_kind: per_share
161
+ scale: unit
162
+ currency: USD
163
+ precision: 1
164
+ order: 2
165
+ aggregation: "median"
166
+ null_policy: "skip"
167
+ source:
168
+ kind: "metric"
169
+ metric_id: "eps_avg"
170
+ - key: "eps_fy3"
171
+ label: "FY3 EPS"
172
+ display:
173
+ numeric_kind: per_share
174
+ scale: unit
175
+ currency: USD
176
+ precision: 1
177
+ order: 3
178
+ aggregation: "median"
179
+ null_policy: "skip"
180
+ source:
181
+ kind: "metric"
182
+ metric_id: "eps_avg"
183
+ - key: "eps_2y_cagr"
184
+ label: "2Y EPS CAGR"
185
+ # arrives fraction
186
+ display:
187
+ numeric_kind: percentage
188
+ scale: unit
189
+ precision: 1
190
+ order: 4
191
+ aggregation: "median"
192
+ null_policy: "skip"
193
+ source:
194
+ kind: "derived"
195
+ derived_formula: "(eps_fy3 / eps_fy1) ** 0.5 - 1"
196
+ - name: "P/E multiples"
197
+ order: 4
198
+ metrics:
199
+ - key: "pe_ltm"
200
+ label: "P / E"
201
+ display:
202
+ numeric_kind: multiple
203
+ scale: unit
204
+ precision: 1
205
+ order: 1
206
+ aggregation: "median"
207
+ null_policy: "skip"
208
+ source:
209
+ kind: "metric"
210
+ metric_id: "reported_pe_ratio"
211
+ - key: "pe_fy1"
212
+ label: "FY1 P / E"
213
+ display:
214
+ numeric_kind: multiple
215
+ scale: unit
216
+ precision: 1
217
+ order: 2
218
+ aggregation: "median"
219
+ null_policy: "skip"
220
+ source:
221
+ kind: "derived"
222
+ derived_formula: "price / eps_fy1"
223
+ - name: "Dividend"
224
+ order: 5
225
+ metrics:
226
+ - key: "dividend_yield"
227
+ label: "Dividend Yield"
228
+ # arrives fraction
229
+ display:
230
+ numeric_kind: percentage
231
+ scale: unit
232
+ precision: 1
233
+ order: 1
234
+ aggregation: "median"
235
+ null_policy: "skip"
236
+ source:
237
+ kind: "metric"
238
+ metric_id: "dividend_yield"
239
+ - key: "dividend_per_share"
240
+ label: "Dividend / Share"
241
+ display:
242
+ numeric_kind: per_share
243
+ scale: unit
244
+ currency: USD
245
+ precision: 1
246
+ order: 2
247
+ aggregation: "median"
248
+ null_policy: "skip"
249
+ source:
250
+ kind: "metric"
251
+ metric_id: "dividend_per_share"
252
+ - key: "dividends_paid_ltm"
253
+ label: "Dividends Paid ($M)"
254
+ display:
255
+ numeric_kind: currency
256
+ scale: millions
257
+ currency: USD
258
+ precision: 1
259
+ order: 3
260
+ aggregation: "median"
261
+ null_policy: "skip"
262
+ source:
263
+ kind: "concept"
264
+ concept_id: "dividends_paid"
@@ -0,0 +1,15 @@
1
+ section_order,section_name,metric_order,metric_key,label,aggregation,null_policy,source_kind,concept_id,metric_id,kpi_key,derived_formula
2
+ 1,Growth,1,revenue,Revenue,median,skip,concept,revenue,,,
3
+ 1,Growth,2,sales_growth,Sales Growth,median,skip,derived,,,,revenue / revenue_prior - 1
4
+ 2,Customer Base / Volume,1,total_employees,Total Associates Employed,median,skip,kpi,,,total_employees,
5
+ 2,Customer Base / Volume,2,loyalty_program_members,Annual Households Served,median,skip,kpi,,,loyalty_program_members,
6
+ 2,Customer Base / Volume,3,total_stores_in_operation,Total Supermarkets Operated,median,skip,kpi,,,total_stores_in_operation,
7
+ 2,Customer Base / Volume,4,new_store_openings,Cumulative New Store Openings,median,skip,kpi,,,new_store_openings,
8
+ 2,Customer Base / Volume,5,comparable_sales_growth,Identical Sales Growth Ex-fuel Ex-Labor Dispute,median,skip,kpi,,,comparable_sales_growth,
9
+ 2,Customer Base / Volume,6,gross_margin_rate,GAAP Gross Margin Rate,median,skip,kpi,,,gross_margin_rate,
10
+ 3,Investment,1,sm_expense,S&M Expense,median,skip,concept,sales_and_marketing,,,
11
+ 3,Investment,2,rd_expense,R&D Expense,median,skip,concept,research_and_development,,,
12
+ 3,Investment,3,capex_ratio,CapEx / Revenue,median,skip,metric,,capex_to_revenue,,
13
+ 4,Margins,1,ebitda_margin,EBITDA Margin,median,skip,metric,,ebitda_margin,,
14
+ 4,Margins,2,fcf_margin,FCF Margin,median,skip,derived,,,,freeCashFlow / revenue
15
+ 4,Margins,3,fcf_conversion,FCF Conversion,median,skip,metric,,fcf_conversion,,