@uhero/feasim 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/README.md +265 -2
- package/dist/index.cjs +5265 -0
- package/dist/index.d.cts +507 -0
- package/dist/index.d.ts +507 -0
- package/dist/index.js +5124 -0
- package/package.json +38 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 UHERO
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,266 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @uhero/feasim
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A development feasibility model for housing on Oʻahu, from the University of Hawaiʻi Economic Research Organization (UHERO).
|
|
4
|
+
|
|
5
|
+
Give it a lot (its size, zoning and height limit) and a policy pathway, such as current zoning, Bill 7 or a transit-oriented development (TOD) permit. It works out what a developer could build there, then runs the project's pro forma: units, parking, costs, rents and sales, financing, residual land value and developer returns. It finishes with a verdict: is the project strong, marginal or poor?
|
|
6
|
+
|
|
7
|
+
Some things you can use it for:
|
|
8
|
+
|
|
9
|
+
- **Compare pathways for one lot:** Which policy makes a project pencil?
|
|
10
|
+
- **Test assumptions:** How do land cost, construction cost, interest rates or a subsidy change the answer?
|
|
11
|
+
- **Run many parcels:** Map or total the housing a policy would make feasible across a district or the whole island.
|
|
12
|
+
|
|
13
|
+
**Note:** The model code is in plain TypeScript with no runtime dependencies, so it runs in Node or the browser.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npm install @uhero/feasim
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
> Until the first release is on npm, install from source: clone this repo, run `npm install` and `npm run build` in it, then run `npm install /path/to/policy-simulator-package` from your project.
|
|
22
|
+
|
|
23
|
+
## Quick start
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { calcParcel, overallLevel } from "@uhero/feasim";
|
|
27
|
+
|
|
28
|
+
// a 20,000 sqft lot zoned BMX-3 with a 150 ft height limit
|
|
29
|
+
const parcel = { lot_sqft: 20000, zone_class: "BMX-3", height: 150 };
|
|
30
|
+
|
|
31
|
+
const result = calcParcel("bill6", parcel);
|
|
32
|
+
|
|
33
|
+
// this returns your results for these example fields
|
|
34
|
+
result.max_units;
|
|
35
|
+
result.dev_roc;
|
|
36
|
+
result.rlv_sqft;
|
|
37
|
+
overallLevel(result);
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Concepts
|
|
41
|
+
|
|
42
|
+
### Pathways
|
|
43
|
+
|
|
44
|
+
A pathway is a set of rules a project can be built under. Each pathway sets what can be built on the lot and carries its own default assumptions.
|
|
45
|
+
|
|
46
|
+
| `program` | Pathway | What it changes |
|
|
47
|
+
| --- | --- | --- |
|
|
48
|
+
| `"bill6"` | Standard (current zoning) | The zone's FAR (as set by Ordinance 26-7), lot coverage and height under the Land Use Ordinance, with the Article 6 parking minimums |
|
|
49
|
+
| `"standard"` | Before Ord. 26-7 | The FAR and lot coverage rules as they stood before Ordinance 26-7, for comparison |
|
|
50
|
+
| `"bill7"` | Bill 7 | An affordable housing pathway in the A, AMX, BMX and Waikiki (Apart, ApartMix) districts, with FAR 4.0 and a 60 ft height cap, construction grants, waived impact fees, and no required parking. Timing, excavation and stepbacks follow the zone |
|
|
51
|
+
| `"tod"` | Transit-oriented development | PD-T and IPD-T permits near rail stations (ROH § 21-9.100): FAR and height bonuses by tier, station area plan limits, no required parking |
|
|
52
|
+
| `"h201"` | HRS § 201H | Exemptions for affordable housing projects, on current (Ord. 26-7) zoning FAR and lot coverage. The defaults make 51% of units affordable at 80% of area median income (AMI), with market units at 120% AMI; set `affordability` and `affordable_ami` to change that. |
|
|
53
|
+
|
|
54
|
+
**Note:** the ids are historical. `"bill6"` is current zoning, and `"standard"` is the rules before Ordinance 26-7.
|
|
55
|
+
|
|
56
|
+
### Parcels
|
|
57
|
+
|
|
58
|
+
`calcParcel` takes a parcel:
|
|
59
|
+
|
|
60
|
+
| Field | Required | Meaning |
|
|
61
|
+
| --- | --- | --- |
|
|
62
|
+
| `lot_sqft` | yes | Lot area, sqft |
|
|
63
|
+
| `zone_class` | yes | Zoning district, such as `"A-2"`, `"BMX-3"` or `"I-2"` |
|
|
64
|
+
| `height` | yes | Height limit, ft. Use `0` if unknown, and the pathway's default is used. |
|
|
65
|
+
| `tmk` | no | An ID such as the tax map key, copied onto the result |
|
|
66
|
+
| `parking_exempt` | no | No off-street parking required (ROH § 21-6.20(a)). Applies to `standard`, `bill6` and `h201`. |
|
|
67
|
+
| `tod_regime` | TOD | `"pdt"` (in an adopted TOD special district) or `"ipdt"` (elsewhere). Leave it out if the lot can't get a TOD permit. |
|
|
68
|
+
| `slu_urban` | TOD | `true` if the lot is entirely in the State Land Use urban district |
|
|
69
|
+
| `station_area`, `tod_precinct` | TOD | The rail station area, and `"core"` (within ¼ mile) or `"tiz"` (¼ to ½ mile) |
|
|
70
|
+
| `tod_map_bonus_height` | TOD | PD-T: the bonus height in parentheses on the TOD zoning map, ft |
|
|
71
|
+
|
|
72
|
+
### Assumptions (Levers)
|
|
73
|
+
|
|
74
|
+
Everything else comes from the pathway's defaults, `DEFAULT_INPUTS[program]`. "Levers" is the name of the third argument of `calcParcel` and `parcelInputs` in `src/parcels.ts`:
|
|
75
|
+
`calcParcel(program, parcel, levers = {}, opts = {})`. Change any of them by passing levers as the third argument:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
calcParcel("bill6", parcel, {
|
|
79
|
+
// $ per sqft of lot
|
|
80
|
+
land_cost: 250,
|
|
81
|
+
// construction, $ per sqft of building
|
|
82
|
+
hard_cost_per_sqft: 500,
|
|
83
|
+
// permanent loan interest rate
|
|
84
|
+
permanent_rate: 0.065,
|
|
85
|
+
// equity share of the construction financing
|
|
86
|
+
equity: 0.35,
|
|
87
|
+
// ground-floor retail, where the zone permits it
|
|
88
|
+
include_retail: true,
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Note:** `height_limit` and `max_far` override the pathway's envelope. Bill 7 still caps height at 60 ft. See the `SliderInputs` type for the full list, with units.
|
|
93
|
+
|
|
94
|
+
### Input values
|
|
95
|
+
|
|
96
|
+
The model doesn't check its inputs. A value it doesn't recognize falls back to a default or gives an empty result without an error, so use these exactly:
|
|
97
|
+
|
|
98
|
+
| Input | Use | If it's wrong |
|
|
99
|
+
| --- | --- | --- |
|
|
100
|
+
| `program` | `"bill6"`, `"standard"`, `"bill7"`, `"tod"` or `"h201"`, lowercase | Anything else runs `"bill6"` |
|
|
101
|
+
| `zone_class` | The district code as the Land Use Ordinance writes it, uppercase: `"A-2"`, `"AMX-3"`, `"BMX-4"`, `"B-2"`, `"I-2"`; Waikiki is `"Apart"` or `"ApartMix"` | `"a-2"` or an unknown code has no FAR, so `calcParcel` builds nothing; with your own `max_far` it builds, but without the zone's coverage, timing and retail rules |
|
|
102
|
+
| `affordable_ami`, `market_ami` | `"ami_"` plus a bracket from 10 to 140 in steps of 10, such as `"ami_80"` | Other numbers (`"ami_85"`) are scaled from the 50% row and won't match the published HHFDC limits |
|
|
103
|
+
| `unit_type` | `"studio_100"`, `"studio75_1br25"`, `"studio50_1br50"`, `"studio25_1br75"`, `"one_bb_100"`, `"one_br50_2br50"`, `"two_bb_100"` or `"third_each"` | Anything else is treated as all one-bedroom |
|
|
104
|
+
| `affordability` (201H) | `0.51` or more | Below 50% + 1 of units, `calcSingle` throws |
|
|
105
|
+
| `permanent_rate`, `amort_years` | Above `0` | `0` makes the IRR, equity multiple, profit and ROI `NaN` |
|
|
106
|
+
|
|
107
|
+
With `calcSingle` and `"tod"`, pass a `max_far`, or the model can't run the project without the open-space bonus to compare (see Model notes). `calcParcel` always passes one.
|
|
108
|
+
|
|
109
|
+
### Results
|
|
110
|
+
|
|
111
|
+
Every function returns a `ParcelOutput`. Some of its fields:
|
|
112
|
+
|
|
113
|
+
| Group | Fields |
|
|
114
|
+
| --- | --- |
|
|
115
|
+
| Building | `max_units`, `buildable_sqft`, `net_residential_sqft`, `net_commercial_sqft`, `height_limit` |
|
|
116
|
+
| Units | `affordable_units`, `mkt_rent_units`, `afd_rent_units`, `mkt_sale_units`, `afd_sale_units` |
|
|
117
|
+
| Parking | `parking_stalls`, split into `surface_stalls`, `ground_floor_stalls` and `podium_stalls`; `podium_levels`, `loading_spaces`, `bicycle_spaces` |
|
|
118
|
+
| Costs | `hard_cost`, `soft_cost` (itemized in `soft_cost_breakdown`: permits, fees, water charges, construction management), `dev_cost`, `total_financing_cost` |
|
|
119
|
+
| Income | `egi` (effective gross income), `noi` (net operating income), `gdv` (gross development value) |
|
|
120
|
+
| Returns | `dev_roc` (return on cost), `dev_levered_irr`, `dev_equity_multiple`, `dev_profit`, `dev_dscr` |
|
|
121
|
+
| Land | `rlv` and `rlv_sqft`: the residual land value, or the most a developer could pay for the land and still hit their target return |
|
|
122
|
+
|
|
123
|
+
**Note:** Rates are fractions, so `0.058` means 5.8% and money is in dollars.
|
|
124
|
+
|
|
125
|
+
### The verdict
|
|
126
|
+
|
|
127
|
+
`overallLevel(result)` scores four metrics:
|
|
128
|
+
|
|
129
|
+
| Metric | Poor | Marginal | Strong |
|
|
130
|
+
| --- | --- | --- | --- |
|
|
131
|
+
| Return on cost | more than 1 point below the target | within 1 point | at or above the target |
|
|
132
|
+
| Residual land value ÷ land price | under 90% | 90–110% | 110% or more |
|
|
133
|
+
| Equity multiple | under 2.0× | 2.0–2.5× | 2.5× or more |
|
|
134
|
+
| Levered IRR | under 8% | 8–12% | 12% or more |
|
|
135
|
+
|
|
136
|
+
The project is **poor** if half or more of the metrics are poor, **strong** if none are poor and at least three of the four are strong, and **marginal** otherwise. `rocLevel`, `rlvLevel`, `equityMultipleLevel` and `irrLevel` give each metric on its own.
|
|
137
|
+
|
|
138
|
+
## Some Examples
|
|
139
|
+
|
|
140
|
+
1. Compare pathways for one lot
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
import { calcParcel, overallLevel, type ProgramId } from "@uhero/feasim";
|
|
144
|
+
|
|
145
|
+
const parcel = { lot_sqft: 20000, zone_class: "BMX-3", height: 150 };
|
|
146
|
+
|
|
147
|
+
for (const program of ["bill6", "bill7", "h201", "tod"] as ProgramId[]) {
|
|
148
|
+
const r = calcParcel(program, parcel);
|
|
149
|
+
console.log(program, r.max_units, r.dev_roc, overallLevel(r));
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
2. Test how sensitive a result is to land cost
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
for (const land_cost of [100, 200, 300]) {
|
|
157
|
+
const r = calcParcel("bill6", parcel, { land_cost });
|
|
158
|
+
console.log(land_cost, overallLevel(r));
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
3. Run many parcels
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
const results = parcels.map((p) => calcParcel("tod", p));
|
|
166
|
+
const feasibleUnits = results
|
|
167
|
+
.filter((r) => overallLevel(r) === "strong")
|
|
168
|
+
.reduce((sum, r) => sum + r.max_units, 0);
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
4. Change TOD permit tiers, the fourth argument picks the tier: `"base"`, `"minor_sd"`, `"major_sd"` or `"with_benefits"`
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
import { calcParcel, todIneligibleReason } from "@uhero/feasim";
|
|
175
|
+
|
|
176
|
+
const site = {
|
|
177
|
+
lot_sqft: 20000, zone_class: "BMX-3", height: 150,
|
|
178
|
+
tod_regime: "pdt", slu_urban: true, tod_precinct: "core",
|
|
179
|
+
} as const;
|
|
180
|
+
|
|
181
|
+
// null if the lot can use a TOD permit
|
|
182
|
+
todIneligibleReason(site);
|
|
183
|
+
calcParcel("tod", site, {}, { tier: "with_benefits" });
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
5. A hypothetical lot, with no parcel.
|
|
187
|
+
**Note:** `calcSingle` is the model itself: one flat set of inputs in, one result out. Use it when you want to set the FAR and height yourself instead of taking them from a pathway's rules.
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
import { calcSingle, DEFAULT_INPUTS } from "@uhero/feasim";
|
|
191
|
+
|
|
192
|
+
calcSingle({
|
|
193
|
+
...DEFAULT_INPUTS.bill6,
|
|
194
|
+
lot_sqft: 10000, zone_class: "A-2", height_limit: 40, max_far: 3.0,
|
|
195
|
+
});
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## API
|
|
199
|
+
|
|
200
|
+
| Export | What it does |
|
|
201
|
+
| --- | --- |
|
|
202
|
+
| `calcParcel(program, parcel, levers?, opts?)` | Run a parcel under a pathway. `opts.tier` sets the TOD tier. |
|
|
203
|
+
| `parcelInputs(program, parcel, levers?, opts?)` | The full set of inputs `calcParcel` would run, without running it |
|
|
204
|
+
| `pathwayEnvelope(program, parcel, opts?)` | The height limit and FAR a pathway gives a parcel |
|
|
205
|
+
| `calcSingle(inputs)` | The model: `SliderInputs` in, `ParcelOutput` out |
|
|
206
|
+
| `DEFAULT_INPUTS[program]` | Each pathway's default assumptions |
|
|
207
|
+
| `overallLevel(result)` | The verdict: `"strong"`, `"marginal"` or `"poor"` |
|
|
208
|
+
| `todIneligibleReason(parcel)`, `todNotApplicableReason(parcel, tier)` | Why a lot can't use a TOD permit, or can't have housing at a tier |
|
|
209
|
+
|
|
210
|
+
The package also exports the rules behind the model, such as the zoning FAR tables, the TOD rules, the parking and loading requirements, and the water charges, for use in your own calculations. Their TypeScript types document each one.
|
|
211
|
+
|
|
212
|
+
## Model notes
|
|
213
|
+
|
|
214
|
+
- The model is for only for Oahu as of 10/12/2026.
|
|
215
|
+
- The zoning, fees, rents, sale prices and costs are for the City and County of Honolulu.
|
|
216
|
+
- Every pathway is rental by default
|
|
217
|
+
- The defaults are 100% rental. Set `rental_split` and `sale_split` to model for-sale units in later versions.
|
|
218
|
+
- The model is designed to be a first pass feasibility screen, not an appraisal
|
|
219
|
+
- The model estimates whether a typical project could pencil, it does not design a specific building.
|
|
220
|
+
- The open-space FAR bonus is optional
|
|
221
|
+
- Where a zone offers one (B-2, BMX-3, BMX-4), `calcSingle` runs the project with and without it and keeps the one with the higher residual land value, or the bigger building when neither pencils. The bonus costs 1 sqft of public open space per 5 sqft of extra floor area, so it often isn't worth it on lower buildings. `open_space_bonus` on the result is the bonus taken.
|
|
222
|
+
|
|
223
|
+
The model's logic and data sources are documented here:
|
|
224
|
+
|
|
225
|
+
- [Policy Simulator Documentation](https://docs.google.com/document/d/1HwELgrw0IncCjsLrZh_BB0OYnBjdNXKX-kQArnLvL5k/edit?usp=sharing): how the model works
|
|
226
|
+
- [Pro-Forma Variables Spreadsheet V2](https://docs.google.com/spreadsheets/d/1qVd_1RI3wjlhwmG2LTzQK0v08qlD47YCn9gULIG-1-4/edit?usp=sharing): the variables in use and their sources
|
|
227
|
+
- [Assumptions Made Tracker](https://docs.google.com/spreadsheets/d/1QqSswndNPDVZwGPuKo3mlO7poFdPswB8rq8TlTXpmyo/edit?gid=0#gid=0): assumptions made in building the policy framework
|
|
228
|
+
|
|
229
|
+
Background reading:
|
|
230
|
+
|
|
231
|
+
- [201-H Process Summary](https://dbedt.hawaii.gov/hhfdc/files/2023/05/2023-05-18-201H-Process-FINAL.pdf): 201H and its requirements
|
|
232
|
+
- [Parcels & Zoning Dashboard](https://cchnl.maps.arcgis.com/apps/webappviewer/index.html?id=96d4994d61cc495fa1a8524b3b116eee): an interactive map of Oʻahu's parcels
|
|
233
|
+
- [Honolulu Inclusionary Housing Flowchart](https://miro.com/app/board/uXjVJaPZAJQ=/)
|
|
234
|
+
- [Development Flowchart](https://miro.com/app/live-embed/uXjVJDi57jk=/?embedMode=view_only_without_ui&moveToViewport=-2298%2C-1870%2C3822%2C2086&embedId=247174178665): the housing development process
|
|
235
|
+
- [Pro Forma Variables Spreadsheet V1](https://docs.google.com/spreadsheets/d/1_sSlavakP_3b8Ssv547RV-63rOuHqrqrIfim9JED3XU/edit?usp=sharing): the older version, with every variable and its source
|
|
236
|
+
|
|
237
|
+
## Development
|
|
238
|
+
|
|
239
|
+
```sh
|
|
240
|
+
npm install # install dev dependencies
|
|
241
|
+
npm test # run the tests
|
|
242
|
+
npx vitest run -u # re-record the pinned results after a deliberate change
|
|
243
|
+
npm run build # build dist
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Tests
|
|
247
|
+
|
|
248
|
+
The tests are in `test/`:
|
|
249
|
+
|
|
250
|
+
- **`model.test.ts`** pins every output of the model for each pathway: its defaults, scenarios that exercise its branches (grants, the Bill 7 tax exemption, park dedication, retail, impact fees, financing, the envelope), and a sample of Oahu parcels (every zone, with the smallest, median and largest lot and the tallest height). It also checks rules that must hold whatever the numbers. For example, the same inputs give the same outputs, inputs are never modified, unit counts are whole and consistent, and policy rules such as Bill 7's 60 ft cap, its park waiver and the TOD permit tiers.
|
|
251
|
+
- **`calculator.test.ts`** runs single hypothetical lots through `calcSingle` (`test/data/calculator-defaults.json`): each pathway's defaults, with retail on and off and under each TOD regime and tier. It checks the Article 6 parking defaults, and that the FAR and height match what `pathwayEnvelope` gives the same lot.
|
|
252
|
+
- **`gauge.test.ts`** runs 287 Oahu parcels through `calcParcel` (`test/data/gauge-parcels.json`), including TOD parcels in every regime, station area and industrial district, PD-T parcels with bonus heights, and parcels with and without the parking exemption. It pins the per-parcel results and totals, and checks the rules on every parcel: exempt parcels build no stalls and the rest the legal minimum, bicycle parking outside industrial zones, no loading on lots of 7,500 sqft or less, the PD-T 40 ft front strip, the IMX-1 unit cap, and the TOD status of industrial land.
|
|
253
|
+
|
|
254
|
+
Any change that moves a result fails the snapshot tests, on purpose. If the change is intended, run `npx vitest run -u`, review the snapshot diff in git, and commit it with the change.
|
|
255
|
+
|
|
256
|
+
### Where the model comes from
|
|
257
|
+
|
|
258
|
+
UHERO's Policy Simulator dashboard is one application built on this model. Its source is in UHERO's private analytics repository, and this package is the public copy.
|
|
259
|
+
|
|
260
|
+
| Here | In `UHERO/analytics` |
|
|
261
|
+
| --- | --- |
|
|
262
|
+
| `src/calculations/*.ts` | `app/housing/policy/calculator/calculations/*.ts` (unchanged, except that the output also returns `soft_cost_breakdown`) |
|
|
263
|
+
| `src/feasibility.ts` | `app/housing/policy/shared/feasibility.ts` (import path only) |
|
|
264
|
+
| `src/parcels.ts` | The Policy Gauge's pathway rules (`gauge/calculations/calc-*.ts`) and default assumptions (`gauge/dashboard.tsx`), as one module; identical results for every parcel |
|
|
265
|
+
|
|
266
|
+
**Note:** Currently copied from analytics commit `c7d7774b` (branch `gauge-v11-sakura`, 10/9/26). Until analytics uses this package directly, make any model change in both places.
|