Repiano Specification and User Guide
About this Document
This document is to describe Reason Boards, Repiano, and the reasoning and details of Repiano's implementation. I hope that this is useful for humans, AIs, and humans working with AIs to be able to understand and conceptualize this tool, provide AIs with significant immediate context, and let AIs aid humans in their work experimenting with this prototype.
What are Reason Boards and what is Repiano
I believe that there is a missing "overpowered pro/con list" product. I'm calling it a Reason Board, in the same way that someone might have a Task Board or Brainstorming Board or Project Board.
Reason boards help people with repeated decisions over a large set of choices. Collect choices into a 'collection,' recording for each choice objective and subjective characteristics that factor into making that choice. Create a 'choice' definition, listing a set of reasons why you would choose one option over another and putting subjective numbers to each option. Then as repeated choices are made, keep track of those decisions in a 'history' which then can be used in choices.
Reason boards are meant to be as flexible as spreadsheets - add, edit, and remove data at any time. (Careful! Just like deleting a critical column in a spreadsheet will break your functions, you can break and need to repair your choices too.)
Change reasons on the fly. Play with the weights of reasons in the UI to float interesting ideas and choices. And finally, decide what to make of the results - helpful and valid, unhelpful and misleading - and make your own decision what choice really makes the most sense.
I've built a rough prototype that I've named Repiano ("REP-pee-AH-no", some Brits' misspelling of the Italian term for "ripieno" (literally "padding") to mean "the full ensemble" in historical music). Note that Repiano is a very rough, 'pre-alpha' prototype. It's under very active development, adding necessary features but also reconsidering assumptions and concepts (e.g. are the abstractions of the reasons correct? Is it correct to separate out into choices, histories, and collections?). This document works to document what currently exists as accurately as it can.
Repiano is a rough prototype, and can break existing functionality at any time. It defines and stores all data of choices, histories, and collections and JSON objects with the intention to be flexible. Data is stored in the browser's local storage, not on a server, and clearing your browser data can remove all your data - it's one of the reasons why I've prioritized importing and exporting JSON objects - do it often!
Repiano Concepts Overview
Collection
A collection is merely a table of data that can be used to choose from. Rows are items to select from, and columns are text and number data that represent those items.
Update collection data at any time. Add new columns, add new rows, and edit data as you see fit. It is assumed that over time you will want to add new properties and new information to continue to add nuance to your choices.
One column, which in the UI is displayed first - is the official name of that choice and is called the "Id Property". It must be unique and is assumed to be stable over time. It is the label in the choice and the stored value in history to represent the item. It is possible to change this value - if it is changed, then the assumption is that the row now represents a wholly new option, and the previous value in history was a valid option but no longer available. If a row's Id Property is changed to a value that previously existed, it is assumed that it is exactly that thing in history. (In order to correctly fully update an Id Property, change it in the collection, and then change every reference to it in history. Probably at some point I'll decide to stop doing this and use internal guid ids like normal, but using the literal descriptive string as the Id in history is helpful for prototyping.)
History
A history is a table of data representing the history of a choice. It contains a date timestamp (editable with a date picker) in the format YYYY-MM-DD (e.g., 2025-09-18), an internal ID that's not displayed, and a collection property that represents the choice that was made on that date. Additional properties can be added to be used as notes, data, or other information that can be used for choices.
(TODO: Over time, data and reasons can and should change. In order to make those changes affect current and future actions, but not rewrite history, a copy of the chosen item and the reasons used to select it are saved in history at the time of selection. For example, if you originally like a restaurant, but then its ownership changes and you dislike it, the history will save that you liked it at the time and any historical reason values will reflect that reality.)
Choice
A choice references a collection to choose from and a history of past choices and contains a list of reasons. The reasons take the information in the collection and history and calculate an arbitrary unitless number representing how high or low a given choice is on each metric. Each reason also has a weight that can be easily increased to multiply the unitless number by some factor, so you can see how results change if you give one reason much more weight. All the reasons are simply added together to get a total, and the options within the collection are sorted from high to low.
Reasons can be arbitrary and subjective, or concrete and objective. Exact dollar costs or distances might be one reason, and ranked preference or the feeling of getting tired of doing the same thing over again might be another reason. Experimenting with different weights for each rule is vital to explore what feels right in the balance of subjective and objective reasons (and perhaps help yourself or a group of people more concretely debate the tradeoffs the group is making). (Note that if all your reasons are completely objective, then you likely have a problem where existing solvers or optimizers can make quick work on.)
Property Reason Type
Property reasons are among the simplest types: prefer an option in your collection due to the literal numerical property value on the collection. In your collection, provide numbers (absolute, subjective, or in between) representing how good something is (how much someone likes it subjectively, how close it is to your house). That value will be used exactly in the collection.
Like all reasons, the weight can be scaled arbitrarily, so the relative values are more important than absolute. The top-level scaling parameter can also be used to apply power functions to compress or expand the value range (e.g., scaling of 0.5 for square root, 2.0 for squaring).
Note that this reason focuses on just this single choice in isolation, not how this accumulates over time (i.e. a benefit reason). Often this reason is about the preference/convenience/logistics of this choice in isolation - a preference that is nearly always unchanged regardless of the overall context. The other reasons focus more on the context in history.
Recency Reason Type
Recency reasons prefer options due to how long it's been since you've done that thing or category of thing in history. Typically, when repeatedly choosing options (what to eat, what to do, what to work on), variety is preferred for many reasons (tired of the same old thing, want to try something you've not done in a while, need to mix it up). Recency reason types let you increase the score of choices based on how long ago you've done them.
Recency types can set a property (defaulting to the Id Property of the given collection). The recency reason will look back into history and see when it can find that exact property value in history. The duration can be measured in two ways:
- Days (default): Uses the number of days since the item was last selected. This is useful for time-based recency where you want to prefer items you haven't done recently in calendar time.
- Entries: Uses the index position in history (0 for the most recent entry, 1 for the second most recent, etc.). This is useful when you want recency based on how many other items you've chosen since, regardless of the actual time elapsed.
Unlike other reasons, you often will choose string properties in the recency reason, allowing you to build a reason around the most recent time you've had 'Italian' or 'Mexican' cuisine. You can also choose number properties, finding the most recent time you've chosen something with a specific numeric value. Note that recency is based only on the single most recent occurrence that matches, disregarding all history before that most recent time. If you'd rather factor in all instances across history, benefit reasons with discounted history often better match those needs.
The top-level scaling parameter can be used to apply power functions to the recency score (e.g., scaling of 0.5 for square root to compress differences, or higher values to expand them).
Benefit Reason Type
Benefit reasons prefer an option in your collection because a numerical property on the item represents progress or a benefit that choices have over time. One way to think of a benefit reason is a way to represent the 'need meters' in the Sims. Sims might have a food meter that goes down over time, and some activities might decrease it in different ways and eating different foods will increase it in other ways.
Benefits can be configured in three main ways: duration transforms, targets, and utility shaping. By default, benefits have no targets or duration transforms, as if a property is a raw endless supply of a resource and gaining 5 of that resource is always valued the same regardless of context.
Duration transforms reduce (or modify) the value something gained based on how much time or how many entries have elapsed since the gain. The duration can be measured in two ways (controlled by durationType):
- Days (default): Uses the number of days since the history item was recorded
- Entries: Uses the index position in history (0 for most recent, 1 for second most recent, etc.)
Duration transforms can take different shapes (controlled by durationShape):
- none (default): No transformation, all history items weighted equally
- hyperbolic: Hyperbolic discounting that effectively puts a 'half life' on a gain. The
durationScalingparameter controls how many days/entries it takes to halve the property (e.g., scaling of 3 means 1/2 at 3 units, 1/3 at 6 units). This effectively naturally lowers the overall benefit gained over time, much like a Sim's food meter getting lower and lower the longer it's been since they've had all the meals they've eaten. - powerFunction: Power function transform using the formula
value * (1 + duration)^durationScaling. TypicallydurationScalingshould be negative (e.g., -1, -0.5) to discount past entries - the more negative, the faster past values decay. However, positive scaling can be used for special cases like approximating interest on past investments (e.g., 1.02 would slightly increase the value of older entries). - exponential: Not yet implemented.
Targets are a value, after duration transforms have been taken into account, of the max value a benefit is. After a target value has been reached, then all future gains are assumed to be zero. This might be like maxing out a happiness or fullness meter - there is nothing more to be gained - but if duration transforms are in effect, the value gained will reduce over time and there may be a benefit in the future.
Utility shaping takes the raw value after duration transforms and targets have taken effect and applies a power function to transform the accumulated totals before computing marginal benefit. A power function with a utilityScaling of less than 1, such as 0.5, reduces how valuable a benefit is the more that is gained, implementing diminishing marginal utility to use economics speak. This is applied to the accumulated benefit totals, which differs from the top-level scaling parameter that is applied to the final marginal benefit value.
(TODO: Benefit reasons in the future will store the benefit at the time each choice was made, so collections can be edited and revised over time and the historical reason benefits will not change.)
Group Reason Type
Group reasons contain more than one sub-reasons. They simply add the reasons together - for the sake of simplifying the UI and condensing multiple related reasons into a single displayed number. Sub-reasons have a weight and configuration that work just as if they were top-level reasons. Group reasons simply consolidate the display of reasons, and group related reasons together.
Balance Reason Type
Balance reasons contain more than one sub-reasons and prefer an option because it helps maintain a ratio of working towards those reasons over time.
Similar to a group reason, it contains sub-reasons that are configurable. However, unlike group reasons or other reasons, the weight of each reason does not sum to the final goal. Instead, that weight is used to find a ratio how much a given reason should have relative to the other reasons within the balance reason.
Balance reasons are most aligned to contain benefit reasons. Benefit reasons without duration transforms, targets, or utility shaping allow you to create a linear tally of multiple factors and promote choices that maintains that tally. So if choices are ranked 1-5 by Alice and Bob respectively, you can promote choices that maintain a 1:1 ratio of Alice's total and Bob's total - if Alice have had her choices recently and now has a higher total, then Bob's choices will be promoted more.
Balance reasons may contain property reasons - for example if you have a series of possible foods to eat, have a collection of grams of carbohydrates and proteins, and want to incentivize foods that are closest to a given ratio of those numbers. Admittedly, I think it is more likely to either use benefit reasons, or use property or benefit reasons directly without a balance reason.
Balance reasons can contain recency reasons, but I'm not really sure the use case for them; selecting a choice drops a given reason's score down to zero, so conceptually it's difficult to wrap my mind around this beyond an odd way to get certain mathematical properties.
Balance reasons can contain different types of reasons, but please experiment and be careful with the different ratios of weights - you may find they are not intuitive (I've admittedly not experimented with this yet and can't provide better advice).
Balance reasons can use the top-level scaling parameter (available on all reason types) to apply a power function to the ratio improvement value. This allows the reason to get stronger and stronger at encouraging a specific ratio the farther away the preferred ratio is.
(TODO: Like similar history todos, I intend to keep track of items as well as the state of reason ratios at selection time, such that changing collection properties does not alter past choices. As it currently stands, changing a collection after the fact can make it look like what was a previously well balanced set of choices was actually lop-sided and now needs major corrections, which is not what I wish to be the default behavior.)
Value Reason Type
Value reasons provide a way to set a hardcoded numerical score for a reason, or to introduce controlled randomness into the decision-making process. This reason type is independent of collection properties and history.
A value reason has two configurable properties: value (defaults to 0) and maximumValue (defaults to 0). If only value is set, or if value is greater than or equal to maximumValue, the reason always returns the hardcoded value.
If maximumValue is set to a value larger than value, then each time the choice is evaluated, the reason returns a random number between value and maximumValue (inclusive). This randomness is deterministic within a single evaluation - all items in the collection will use the same random seed for a given render, ensuring consistent comparison between options. The random seed can be updated by clicking the "Update Random" button in the UI, which will generate new random values for the next evaluation.
Value reasons can be useful for several scenarios:
- Exploration vs Exploitation: Adding a small random factor can help discover overlooked options that might be undervalued by other reasons
- Tiebreaking: When multiple options have similar scores, randomness can help make a decision
- Baseline scoring: Setting a hardcoded positive or negative value to bias all choices in a certain direction
- Testing and experimentation: Introducing controlled variability to understand how sensitive your decision-making is to small changes
Like all reasons, value reasons can be weighted and combined with other reason types in groups or balance reasons. The random number generation uses a Linear Congruential Generator (LCG) for deterministic pseudo-randomness based on the current timestamp seed.
Repiano Technical Implementation Overview
(TODO: The below was almost entirely generated by AI. I've skimmed it and everything seems correct, but I don't know if it's actually helpful.)
Repiano is a rough prototype made with React, Vite and Pico css, and other packages.
Key implementation details:
- Local storage persistence: All data stored in browser local storage with import/export as JSON
- Global state management: Centralized state with UI rebuilding on changes
- JS classes as JSON: Objects serialize directly to/from JSON
- Graceful validation: Missing data or references default to zero with warnings
- Flexible data model: Properties, items, and reasons can be added/edited/removed at any time
The system is designed to be as flexible as spreadsheets - start small with one property and one reason, then iteratively add more as you refine your decision-making process.
Technical Specification: Repiano JSON Objects
Overview
This specification defines the JSON structure and behavior for Collection, History, and Choice objects in the Repiano decision-making system. These objects form a collection-history-choice architecture where Collections store structured data, Histories track usage over time, and Choices compute weighted scores using configurable reasoning algorithms.
Collection Object
Structure
{
"guid": "string",
"name": "string",
"schema": {
"idProperty": "string",
"properties": [
{
"name": "string",
"type": "string|number|collection",
"label": "string (optional)"
}
]
},
"items": [
{
"[propertyName]": "value"
}
]
}
Properties
- guid: Unique identifier for the collection
- name: User-provided display name
- schema.idProperty: Property name that uniquely identifies each item (changeable but affects history tracking)
- schema.properties: Ordered list of property definitions
- name: Property identifier
- type: Data type (
string,number,collection) - label: Optional display label
- items: Array of data objects, each must have unique
idPropertyvalue
Behavior
- Schema validation fails gracefully with warnings for invalid types
- Missing idProperty values are logged and filtered out
- idProperty changes are supported but may orphan history items
- Collections don't reference other collections in current implementation
History Object
Structure
{
"guid": "string",
"name": "string",
"schema": {
"properties": [
{
"name": "string",
"type": "string|number|collection",
"collectionGuid": "string (if type=collection)",
"label": "string (optional)"
}
]
},
"items": [
{
"id": "string (auto-generated)",
"timestamp": "string (ISO YYYY-MM-DD)",
"[propertyName]": "value"
}
]
}
Core Properties (Auto-added)
- id: Auto-generated GUID for each history item
- timestamp: ISO formatted date string (YYYY-MM-DD), type 'date', editable with a date picker in the UI
Behavior
- Items are automatically sorted by timestamp in application logic
- Out-of-order insertion is supported in JSON
- Duplicate entries (same timestamp + collection item) are treated as valid
- Missing collection references degrade gracefully to defaults/zeros
- Extends Collection class, inheriting base functionality
Choice Object
Structure
{
"guid": "string",
"name": "string",
"collectionGuid": "string",
"historyGuid": "string",
"reasons": [
{
"name": "string",
"weight": "number (defaults to 1)",
"scaling": "number (defaults to 1)",
"type": "property|recency|benefit|group|balance|value",
"properties": "object (type-specific)"
}
]
}
Universal Reason Parameters
All reason types support two universal parameters that modify their final computed values:
-
weight (defaults to 1): A multiplier applied to the reason's value. This allows you to increase or decrease the importance of a reason relative to others. For example, a weight of 2.0 doubles the reason's contribution to the total score.
-
scaling (defaults to 1): A power function applied to the reason's value before the weight is applied. The formula is:
finalValue = (|value|^scaling) * sign(value).- Scaling < 1 (e.g., 0.5): Compresses the value range, making differences less pronounced
- Scaling = 1: Linear (no transformation)
- Scaling > 1 (e.g., 2.0): Expands the value range, making differences more pronounced
- The sign is preserved, so negative values remain negative after scaling
Note: Internally, reasons also calculate previous and current values representing state (mostly used by benefit reasons for display purposes). Scaling and weight are applied only to the value used for decision-making, not to these state representations.
Reason Types
Property Reason
Directly uses a numerical property from collection items.
{
"type": "property",
"properties": {
"name": "string",
"default": "number (optional, defaults to 0)"
}
}
Recency Reason
Computes recency scores based on when an item was last selected in history.
{
"type": "recency",
"properties": {
"name": "string (optional, defaults to collectionIdProperty)",
"durationType": "days|entries (optional, defaults to days)",
"default": "number (optional, defaults to 0)"
}
}
- name: The property to use for matching in history (defaults to the collection's ID property). This allows you to track recency by category (e.g., cuisine type) rather than just by item ID.
- durationType: How to measure the duration since last selection:
days(default): Linear score based on number of days since last selectionentries: Score based on the index position in history (0 for most recent, 1 for second most recent, etc.)
- default: Value to use if the item has never been selected
Note: Use the top-level scaling parameter to apply power functions to the recency score (e.g., 0.5 for square root, 2.0 for squaring).
Benefit Reason
Calculates cumulative and incremental benefit using history data.
{
"type": "benefit",
"properties": {
"name": "string",
"default": "number (optional, defaults to 0)",
"target": "number (optional, defaults to infinity)",
"utilityShape": "none|powerFunction (optional, defaults to none)",
"utilityScaling": "number (optional, defaults to 1)",
"durationShape": "none|hyperbolic|powerFunction (optional, defaults to none)",
"durationScaling": "number (optional, defaults to 1)",
"durationType": "days|entries (optional, defaults to days)"
}
}
- target: Maximum benefit threshold
- utilityShape: Transformation applied to accumulated totals before computing marginal benefit
none: No transformation, linear summationpowerFunction: Applies power function to accumulated totals as x^utilityScaling (0-1 creates diminishing marginal utility, >1 creates increasing marginal utility)
- utilityScaling: The exponent used when utilityShape is
powerFunction(defaults to 1) - durationShape: How to transform historical benefits based on duration
none: All history items weighted equallyhyperbolic: Hyperbolic discounting, durationScaling determines halving period (e.g., 3 means 1/2 at 3 units, 1/3 at 6 units)powerFunction: Power function transform as value * duration^durationScaling. Typically negative (e.g., -1, -0.5) to discount past entries; positive values (e.g., 1.02) can model non-compounding interest
- durationScaling: The factor used for duration transforms (defaults to 1)
- durationType: How to measure duration
days(default): Number of days since the history item was recordedentries: Index position in history (0 for most recent, 1 for second most recent, etc.)
Note: The utilityShape/utilityScaling is applied to accumulated totals (implementing diminishing marginal utility), whereas the top-level scaling parameter is applied to the final marginal benefit value. Both can be useful independently.
Group Reason
Hierarchical reason that sums other reasons.
{
"type": "group",
"properties": {
"reasons": ["array of reason objects"]
}
}
Balance Reason
Maintains a target ratio between multiple sub-reasons over time.
{
"type": "balance",
"properties": {
"reasons": ["array of reason objects"]
}
}
- reasons: Array of sub-reasons to maintain in ratio
- Child reason weights determine the target ratio (not used for summing); the
scalingparameter on sub-reasons is ignored - Use the top-level
scalingparameter to apply power functions to the ratio improvement value
Value Reason
Sets a hardcoded value or generates a random number within a specified range.
{
"type": "value",
"properties": {
"value": "number (optional, defaults to 0)",
"maximumValue": "number (optional, defaults to 0)"
}
}
- value: The hardcoded score to return, or the minimum value for the random range
- maximumValue: If greater than
value, enables random number generation betweenvalueandmaximumValue - Random numbers are deterministic per evaluation (same seed for all items in a single render)
- The random seed can be updated via the "Update Random" button in the UI
Behavior
- Reasons computed on-demand when data changes (spreadsheet-like)
- Weights can be any real non-negative value (recommended < 10, no enforcement)
- Missing collection/history references show empty/default state
- No cross-reason dependencies except within groups
- Benefit calculations are unoptimized (prototype implementation)
Cross-Object Relationships
Reference Handling
- Graceful Degradation: Missing references fall back to defaults/zeros with warnings
- Loose Coupling: Objects attempt to find matching GUIDs but continue operating if not found
- No Circular References: Current architecture prevents circular dependencies
- Schema Changes: Handled dynamically like spreadsheets, recalculated on changes
Data Flow
- Collections define structured data sets
- Histories track timestamped interactions with collection items
- Choices compute weighted scores combining collection properties and history patterns
- All objects support independent modification with graceful handling of broken references
Examples
I am working on a series of examples.
-
Fairer Dice - Case for value reason with random values and balance reason with benefit subreasons
-
Food Variety - Case for a recency reasons balanced with a property reason