Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,8 @@ playwright/.auth/
# personal documents kept alongside the repo, not part of the app
Org Docs/
just-tech-application/

# non-code working files that name individuals or record private conversations
# (see ops/AGENTS.md — this is a safety rule, not tidiness)
ops/private/*
!ops/private/README.md
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Directory-specific rules live in nested `AGENTS.md` files and override this one
| [`src/lib/AGENTS.md`](src/lib/AGENTS.md) | Scoring, survey items, validation — the load-bearing logic |
| [`src/components/AGENTS.md`](src/components/AGENTS.md) | Astro vs React, brand tokens, islands |
| [`migrations/AGENTS.md`](migrations/AGENTS.md) | Schema changes and the production migration trap |
| [`ops/AGENTS.md`](ops/AGENTS.md) | Non-code work: growth, funding, partnerships, content |

> **Never put an `AGENTS.md` (or any `.md`) inside `src/pages/`.** Astro's file-based
> routing turns Markdown in `src/pages/` into a public page — a guide at
Expand Down Expand Up @@ -82,6 +83,7 @@ npm test -- scoring # filter by name
npm run build # production build
npm run e2e # fresh local D1 + seed + build + Playwright
npm run db:setup # db:fresh then db:seed (local D1 only)
npm run ops:metrics # regenerate ops/METRICS.md from production (read-only)
```

`npm run smoke` has no default target. Supply an explicit `--environment` and
Expand Down
72 changes: 72 additions & 0 deletions ops/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# `ops/` — Non-Code Work

Growth, funding, partnerships, and content. `.planning/` is engineering milestones; this is
everything else. Start at [`README.md`](README.md).

---

## Hard rules

**1. Never write a named individual into a tracked file.**

Organizations, yes. People, no. No names, no email addresses, no "she said," no notes from
a private conversation. Those go in `ops/private/`, which is gitignored.

This is a safety rule, not a style preference. RateMyPlace's first non-negotiable is
protecting people from retaliation, and the tenant organizers and legal aid staff who help
the project are covered by the same logic as reviewers. If you are drafting outreach
content and need a placeholder, write the role: "the tenant union's organizing lead."

**2. Never hand-type a number that can be queried.**

`ops/METRICS.md` is generated by `scripts/ops-metrics.ts` from the production database.
Do not edit it. Do not copy its figures into other files either — link to it. A number
copied into prose is a number that will be wrong within a month, and wrong numbers in a
grant application are worse than absent ones.

If you need a figure that the script doesn't produce, add it to the script.

**3. Aspiration and fact are labeled, always.**

`MASTER.md` spent months describing fraud controls, velocity alerts, and honeypots that
were never built, because nobody separated the plan from the state. It now uses a
**Built today / Planned** split. Use the same discipline here: a channel that was *tried*
reads differently from one that was *considered*.

**4. Voice: no em dashes.**

`brand.md` v1.4 removed em dashes throughout for consistency with the brand bible. Anything
here that could become public copy follows that. Use commas, colons, or a full stop.

See [`content/MESSAGES.md`](content/MESSAGES.md) for approved language, and `brand.md` §2
for the full voice guidance.

## When updating a file

- Update the `Last reviewed:` date. If you didn't verify it, don't touch the date.
- Record outcomes, not intentions. "Emailed three tenant unions, one replied, meeting set"
is useful. "Plan to reach out to tenant unions" ages into noise.
- Failures are the valuable entries. A channel that didn't work saves the next person
the attempt, and it is the thing nobody writes down.

## Where the truth lives

| Question | Source of truth |
|---|---|
| How many reviews, buildings, landlords? | `ops/METRICS.md` (generated) |
| What does the product actually do? | `MASTER.md` — Built today / Planned split |
| How do we talk about it? | `brand.md` §2, then `ops/content/MESSAGES.md` |
| What is the scoring methodology? | `src/lib/scoring.ts`, published at `/methodology` |
| What is the engineering state? | `.planning/`, then `git log` |

When a document and the code disagree, **the code wins** — root `AGENTS.md` says so, and it
has been right every time it came up.

## Don't

- Don't promise capability the product doesn't have. Every claim in outreach or a grant
narrative must be checkable against `MASTER.md`'s Built section or `ops/METRICS.md`.
- Don't overstate the health weighting. Its real-world effect on scores is under 0.1 in
practice. Saying otherwise is the exact overselling this project exists to avoid.
- Don't create a tracking file for work that isn't happening. An empty template is worse
than nothing; it looks like a system and holds no information.
55 changes: 55 additions & 0 deletions ops/METRICS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Metrics

**Generated 2026-08-29 from the production database.** Do not edit by hand — run:

```bash
npx tsx scripts/ops-metrics.ts
```

Read-only. Every query is a SELECT.

## The number that matters

**53 approved reviews.**

Everything else in [growth/](growth/STRATEGY.md) exists to move this. The site is
methodologically sound and technically healthy; it is thin on evidence.

## Coverage

| Measure | Count |
|---|---|
| Approved reviews | 53 |
| Pending review queue | 0 |
| Verified reviews (proof of address) | 15 |
| Buildings in database | 91 |
| Buildings with at least one review | 51 |
| Buildings with no review yet | 40 |
| Landlords | 27 |
| Property managers | 0 |
| Disputes filed | 0 |

51 of 91 buildings carry a review. A search that returns an
empty building is a visitor who leaves.

## Landlord score coverage

A landlord aggregate is withheld below 3 approved reviews, so a
named party is never scored on thin evidence.

| Approved reviews | Landlords | Score |
|---|---|---|
| 1 | 21 | withheld |
| 2 | 4 | withheld |
| 4 | 1 | shown |
| 5 | 1 | shown |

**2 landlords currently show a score. 25 are withheld.**

This is the clearest statement of the constraint. The threshold is a deliberate fairness
choice, not a bug — but it means landlord pages only become useful with volume.

---

*Regenerated by `scripts/ops-metrics.ts`. If this file's date is old, the numbers are
old — rerun it before quoting any figure in a grant application, a pitch, or a post.*
61 changes: 61 additions & 0 deletions ops/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# ops/ — the non-code work

Everything RateMyPlace needs that isn't source code: getting reviews, funding the work,
building relationships, and saying what the project is.

`.planning/` tracks engineering milestones. This tracks everything else.

## Why this exists

The site is technically healthy and methodologically sound. It is thin on evidence:
**53 approved reviews**, and only 2 of 27 landlords clear the three-review threshold
required to show a score. No code change fixes that. This directory is where that work
lives.

## What's here

| Path | Holds | Refresh |
|---|---|---|
| [`METRICS.md`](METRICS.md) | The real numbers, straight from production | **Generated** — never edit by hand |
| [`growth/`](growth/STRATEGY.md) | Getting reviews. The priority. | When something is tried or learned |
| [`funding/`](funding/PIPELINE.md) | Opportunities, deadlines, reusable narrative | When a deadline moves or a piece of prose proves itself |
| [`partners/`](partners/LANDSCAPE.md) | Organizations and the landscape | When the map changes |
| [`content/`](content/MESSAGES.md) | Approved language and talking points | When a message is tested |
| `private/` | Named individuals, private conversations, funder feedback | **Gitignored. Never tracked.** |

## The privacy rule

**Named individuals and private conversations go in `private/`, which is gitignored.**

This is not bureaucratic. RateMyPlace's first non-negotiable is that people who talk to it
should not face retaliation, and that extends past reviewers to the tenant organizers,
legal aid staff, and city contacts who help. A public list of who spoke to you and what
they said is exactly the artifact this project exists to argue against.

Tracked files may name **organizations**. They may not name **individuals**, quote private
conversations, or record what someone said off the record.

## Keeping it current

Every file carries a `Last reviewed:` date. That is a claim, and it can be wrong.

Two rules, both learned the hard way in this repo — `VERSION.md` sat frozen at
v1.1.0-alpha for seven months, and `MASTER.md` described fraud controls that were never
built:

1. **Numbers are generated, never typed.** Anything countable comes from
`npx tsx scripts/ops-metrics.ts`. If you find yourself typing a figure into a document,
it will be wrong within a month.
2. **When a document and reality disagree, reality wins** — and the document gets fixed in
the same sitting, not added to a list.

Agents maintaining these files: read [`AGENTS.md`](AGENTS.md) in this directory first.

## Refreshing the numbers

```bash
npx tsx scripts/ops-metrics.ts
```

Read-only against production. Run it before quoting any figure in an application, a pitch,
or a post.
106 changes: 106 additions & 0 deletions ops/content/MESSAGES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Messages

**Last reviewed:** 2026-08-29

Approved language. Voice rules come from `brand.md` §2; this is where they get applied to
specific recurring situations.

**Voice rules that apply to everything below:**

- **No em dashes.** `brand.md` v1.4 removed them for consistency with the brand bible.
Use commas, colons, or a full stop.
- Plain, direct, civic. Specific over clever. Active voice.
- Write from the tenant's side of the screen.
- No urgency, no marketing register, nothing that sounds like a startup pitch.
- Credibility comes from restraint and from showing the work.

## The canonical line

> **A public record of rental housing, from the people who know it best.**

This is the primary message, from the brand bible. Do not paraphrase it into something
punchier. The shorter variant "a public record for renters" is retired as a primary line
and survives only as a mid-sentence descriptor.

## Positioning, when someone asks what it is

> We are not Yelp for apartments. We are closer to a public health department with a
> comment section.

Useful because it does two jobs at once: it sets the frame and it heads off the comparison
everyone reaches for first.

## The ask, to a prospective reviewer

Lead with the next tenant, not with civic good. A review does not help the person writing
it, and pretending otherwise is transparent.

> Your review will not help you. You already know what living there was like. It will help
> the person standing in that apartment next year deciding whether to sign.

Follow with the honest cost: it is a 32-item survey and takes real time. Do not soften
that. People who are told it is quick and find it is not will abandon it, and abandoned
reviews are worse than declined ones.

## On anonymity, which is the first question every time

> Reviews are anonymous. We collect exact dates so the data is useful, and we never display
> them: the site shows a season and a year, or a range like "1 to 3 years ago." Your name
> is never shown. Reviews are moderated before publication, and landlords can dispute a
> review through a formal process, but a review is never removed because a landlord
> disagrees with it.

Every clause here is true and checkable. Do not add reassurance beyond it.

**Do not say "we don't store IP addresses."** It is not accurate. Reviewer IPs are never
stored, but IPs are persisted for rate limiting and in admin audit logs. If pressed, say
exactly that.

## On methodology, when credibility is the question

> The survey is adapted from three validated public health instruments rather than
> invented: the Observational Housing Quality Scale, the Physical Housing Quality Scale,
> and the WHO LARES study. Every item, every weight, and every citation is published at
> ratemyplace.org/methodology, including worked examples on real reviews. You can check
> our work.

The methodology page is the strongest asset the project has. Point at it early and often.

## On the health weighting, carefully

Health and safety items carry more weight: pests and mold at 1.5x, structural and climate
at 1.3x, plumbing and building security at 1.2x.

**Do not overstate the effect.** On real data the weighting shifts scores by less than 0.1
in practice. If asked directly:

> The weighting reflects which conditions the research says matter most for health. On our
> current dataset it moves scores only slightly. We publish the formula and the worked
> examples so anyone can see exactly how much it does.

Overselling this is the precise failure the project's own ethics forbid.

## On the size of the dataset

Do not hide it and do not apologize for it.

> The site is early. It holds [N approved reviews — see ../METRICS.md] across Boston and
> New Haven, and most landlords do not yet have enough reviews to show a portfolio score.
> We withhold a landlord's score below three reviews rather than publish a number based on
> one person's experience.

The withholding rule is a strength worth stating. It says the project would rather show
nothing than something unfair, which is the opposite of what people expect from a review
site.

**Never hard-code the count.** Run `npx tsx scripts/ops-metrics.ts` and fill it at use time.

## Things not to say

| Don't | Because |
|---|---|
| "We don't store IP addresses" | Not accurate. See above. |
| "Verified reviews" as a headline claim | Only some are. Unverified reviews are simply unbadged, never labeled unverified. |
| Anything implying we solve affordability | Explicitly out of scope, and claiming it undermines everything else. |
| "Rate My Professor for landlords" | Invites the consumer-review frame the project is built against. |
| Any number not freshly generated | See METRICS.md. |
Loading