Add planning docs for Nostr place reviews
This commit is contained in:
343
doc/nostr/nip-place-reviews.md
Normal file
343
doc/nostr/nip-place-reviews.md
Normal file
@@ -0,0 +1,343 @@
|
||||
# NIP-XX: Place Reviews
|
||||
|
||||
## Abstract
|
||||
|
||||
This NIP defines a standardized event format for decentralized place reviews using Nostr. Reviews are tied to real-world locations (e.g. OpenStreetMap POIs) via tags, and include structured, multi-aspect ratings, a binary recommendation signal, and optional contextual metadata.
|
||||
|
||||
The design prioritizes:
|
||||
|
||||
* Small event size
|
||||
* Interoperability across clients
|
||||
* Flexibility for different place types
|
||||
* Efficient geospatial querying using geohashes
|
||||
|
||||
---
|
||||
|
||||
## Event Kind
|
||||
|
||||
`kind: 30315` (suggested; subject to coordination)
|
||||
|
||||
---
|
||||
|
||||
## Tags
|
||||
|
||||
Additional tags MAY be included by clients but are not defined by this specification.
|
||||
|
||||
This NIP reuses and builds upon existing Nostr tag conventions:
|
||||
|
||||
* `i` tag: see NIP-73 (External Content Identifiers)
|
||||
* `g` tag: geohash-based geotagging (community conventions)
|
||||
|
||||
Where conflicts arise, this NIP specifies the behavior for review events.
|
||||
|
||||
### Required
|
||||
|
||||
#### `i` — Entity Identifier
|
||||
|
||||
Identifies the reviewed place using an external identifier. OpenStreetMap data is the default:
|
||||
|
||||
```
|
||||
["i", "osm:<type>:<id>"]
|
||||
```
|
||||
|
||||
Requirements:
|
||||
|
||||
* For OSM POIs, `<type>` MUST be one of: `node`, `way`, `relation`
|
||||
|
||||
Examples:
|
||||
|
||||
```
|
||||
["i", "osm:node:123456"]
|
||||
["i", "osm:way:987654"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Geospatial Tags
|
||||
|
||||
#### `g` — Geohash
|
||||
|
||||
Geohash tags are used for spatial indexing and discovery.
|
||||
|
||||
##### Requirements
|
||||
|
||||
* Clients MUST include at least one high-precision geohash (length ≥ 9)
|
||||
|
||||
##### Recommendations
|
||||
|
||||
Clients SHOULD include geohashes at the following resolutions:
|
||||
|
||||
* length 4 — coarse (city-scale discovery)
|
||||
* length 6 — medium (default query level, ~1 km)
|
||||
* length 7 — fine (neighborhood, ~150 m)
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
["g", "thrr"]
|
||||
["g", "thrrn5"]
|
||||
["g", "thrrn5k"]
|
||||
["g", "thrrn5kxyz"]
|
||||
```
|
||||
|
||||
##### Querying
|
||||
|
||||
Geospatial queries are performed using the `g` tag.
|
||||
|
||||
* Clients SHOULD query using a single geohash precision level per request
|
||||
* Clients MAY include multiple geohash values in a filter to cover a bounding box
|
||||
* Clients SHOULD limit the number of geohash values per query (e.g. ≤ 30)
|
||||
* Clients MAY reduce precision or split queries when necessary
|
||||
|
||||
Note: Other queries (e.g. fetching reviews for a specific place) are performed using the `i` tag and are outside the scope of geospatial querying.
|
||||
|
||||
---
|
||||
|
||||
## Content (JSON)
|
||||
|
||||
The event `content` MUST be valid JSON matching the following schema.
|
||||
|
||||
### Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"type": "object",
|
||||
"required": ["version", "ratings"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"version": {
|
||||
"type": "integer",
|
||||
"const": 1
|
||||
},
|
||||
"ratings": {
|
||||
"type": "object",
|
||||
"required": ["quality"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"quality": { "$ref": "#/$defs/score" },
|
||||
"value": { "$ref": "#/$defs/score" },
|
||||
"experience": { "$ref": "#/$defs/score" },
|
||||
"accessibility": { "$ref": "#/$defs/score" },
|
||||
"aspects": {
|
||||
"type": "object",
|
||||
"minProperties": 1,
|
||||
"maxProperties": 20,
|
||||
"additionalProperties": { "$ref": "#/$defs/score" },
|
||||
"propertyNames": {
|
||||
"pattern": "^[a-z][a-z0-9_]{1,31}$"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"recommend": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"familiarity": {
|
||||
"type": "string",
|
||||
"enum": ["low", "medium", "high"],
|
||||
"description": "User familiarity: low = first visit; medium = occasional; high = frequent"
|
||||
},
|
||||
"context": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"visited_at": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"duration_minutes": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 1440
|
||||
},
|
||||
"party_size": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 100
|
||||
}
|
||||
}
|
||||
},
|
||||
"review": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string",
|
||||
"maxLength": 1000
|
||||
},
|
||||
"language": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z]{2}(-[A-Z]{2})?$"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"$defs": {
|
||||
"score": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example
|
||||
|
||||
### Restaurant Review Event
|
||||
|
||||
#### Tags
|
||||
|
||||
```
|
||||
[
|
||||
["i", "osm:node:123456"],
|
||||
["g", "thrr"],
|
||||
["g", "thrrn5"],
|
||||
["g", "thrrn5k"],
|
||||
["g", "thrrn5kxyz"]
|
||||
]
|
||||
```
|
||||
|
||||
#### Content
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"ratings": {
|
||||
"quality": 9,
|
||||
"value": 8,
|
||||
"experience": 9,
|
||||
"accessibility": 7,
|
||||
"aspects": {
|
||||
"food": 9,
|
||||
"service": 6,
|
||||
"ambience": 8,
|
||||
"wait_time": 5
|
||||
}
|
||||
},
|
||||
"recommend": true,
|
||||
"familiarity": "medium",
|
||||
"context": {
|
||||
"visited_at": 1713200000,
|
||||
"duration_minutes": 90,
|
||||
"party_size": 2
|
||||
},
|
||||
"review": {
|
||||
"text": "Excellent food with bold flavors. Service was a bit slow, but the atmosphere made up for it.",
|
||||
"language": "en"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Semantics
|
||||
|
||||
### Ratings
|
||||
|
||||
* Scores are integers from 1 to 10
|
||||
* `quality` is required and represents the core evaluation of the place
|
||||
* Other fields are optional and context-dependent
|
||||
|
||||
### Aspects
|
||||
|
||||
* Free-form keys allow domain-specific ratings
|
||||
* Clients MAY define and interpret aspect keys
|
||||
* Clients SHOULD reuse commonly established aspect keys where possible
|
||||
|
||||
---
|
||||
|
||||
## Recommendation Signal
|
||||
|
||||
The `recommend` field represents a binary verdict:
|
||||
|
||||
* `true` → user recommends the place
|
||||
* `false` → user does not recommend the place
|
||||
|
||||
Clients SHOULD strongly encourage users to provide this value.
|
||||
|
||||
---
|
||||
|
||||
## Familiarity
|
||||
|
||||
Represents user familiarity with the place:
|
||||
|
||||
* `low` → first visit or limited exposure
|
||||
* `medium` → occasional visits
|
||||
* `high` → frequent or expert-level familiarity
|
||||
|
||||
Clients MAY use this signal for weighting during aggregation.
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
Optional metadata about the visit.
|
||||
|
||||
* `visited_at` is a Unix timestamp
|
||||
* `duration_minutes` represents time spent
|
||||
* `party_size` indicates group size
|
||||
|
||||
---
|
||||
|
||||
## Interoperability
|
||||
|
||||
This specification defines a content payload only.
|
||||
|
||||
* In Nostr: place identity is conveyed via tags
|
||||
* In other protocols (e.g. ActivityPub, AT Protocol): identity MUST be mapped to the equivalent field (e.g. `object`)
|
||||
|
||||
Content payloads SHOULD NOT include place identifiers.
|
||||
|
||||
---
|
||||
|
||||
## Rationale
|
||||
|
||||
### No Place Field in Content
|
||||
|
||||
Avoids duplication and inconsistency with tags.
|
||||
|
||||
### Multi-Aspect Ratings
|
||||
|
||||
Separates concerns (e.g. quality vs service), improving signal quality.
|
||||
|
||||
### Recommendation vs Score
|
||||
|
||||
Binary recommendation avoids averaging pitfalls and improves ranking.
|
||||
|
||||
### Familiarity
|
||||
|
||||
Provides a human-friendly proxy for confidence without requiring numeric input.
|
||||
|
||||
### Geohash Strategy
|
||||
|
||||
Multiple resolutions balance:
|
||||
|
||||
* efficient querying
|
||||
* small event size
|
||||
* early-stage discoverability
|
||||
|
||||
---
|
||||
|
||||
## Future Work
|
||||
|
||||
* Standardized aspect vocabularies
|
||||
* Reputation and weighting models
|
||||
* Indexing/aggregation services
|
||||
* Cross-protocol mappings
|
||||
|
||||
---
|
||||
|
||||
## Security Considerations
|
||||
|
||||
* Clients SHOULD validate all input
|
||||
* Malicious or spam reviews may require external moderation or reputation systems
|
||||
|
||||
---
|
||||
|
||||
## Copyright
|
||||
|
||||
This NIP is public domain.
|
||||
Reference in New Issue
Block a user