Skip to content

Commit aff60b2

Browse files
graylikemeclaude
andcommitted
docs: update llms.txt, README, and CLAUDE.md for construction reference API
Document new construction reference queries, resolved component types on mechData, equipment builder filters, and equipment-seed scraper command. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 93d88c7 commit aff60b2

File tree

3 files changed

+184
-22
lines changed

3 files changed

+184
-22
lines changed

CLAUDE.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ DATABASE_URL=postgres://postgres:pass@localhost:5432/battletech \
3131
# Add --force to re-import availability even if rows exist
3232
# Add --overrides overrides.json for manual MUL ID→slug mappings
3333

34+
# Run the scraper — equipment stats seeding
35+
DATABASE_URL=postgres://postgres:pass@localhost:5432/battletech \
36+
cargo run -p scraper@0.1.0 --release -- equipment-seed \
37+
--file data/equipment_stats.json
38+
# Add --force to overwrite previously-seeded stats
39+
3440
# Seed database from dump (alternative to running the scraper)
3541
./seed/load.sh # uses DATABASE_URL from .env
3642

@@ -88,7 +94,7 @@ HTTP POST /graphql
8894
- `db/models.rs``Db*` structs derived from `FromRow`, used only inside the db layer
8995
- `graphql/types/*.rs``*Gql` newtypes wrapping `Db*`, with `#[Object]` impls that expose the GraphQL API
9096

91-
**DataLoader** (`graphql/loaders.rs`): `MechDataLoader` uses async-graphql's `dataloader` feature to batch-load `unit_mech_data` rows, preventing N+1 queries when `mechData` is requested in list queries.
97+
**DataLoader** (`graphql/loaders.rs`): `MechDataLoader` batch-loads `unit_mech_data` rows. Seven component-type loaders (`EngineTypeLoader`, `ArmorTypeLoader`, `StructureTypeLoader`, `HeatsinkTypeLoader`, `GyroTypeLoader`, `CockpitTypeLoader`, `MyomerTypeLoader`) resolve FK references from `unit_mech_data` to construction reference tables. `AmmoForLoader` and `AmmoTypesLoader` handle ammo↔weapon relationships on equipment. All use async-graphql's `dataloader` feature to prevent N+1 queries.
9298

9399
**Pagination** (`graphql/pagination.rs`): keyset cursors encoded as `base64("sort_val|id:N")`. The `search` functions in `db/units.rs` and `db/equipment.rs` use `QueryBuilder` for dynamic WHERE clauses and `COUNT(*) OVER()` for total count in a single query.
94100

@@ -102,7 +108,7 @@ HTTP POST /graphql
102108

103109
### `crates/scraper` — Data importer (MegaMek + MUL)
104110

105-
Three subcommands: `megamek`, `mul-fetch`, `mul-import`.
111+
Four subcommands: `megamek`, `mul-fetch`, `mul-import`, `equipment-seed`.
106112

107113
**`megamek`** — Reads a MegaMek `unit_files.zip` and upserts all units into Postgres.
108114

@@ -121,6 +127,8 @@ The scraper maintains an in-process `HashMap<slug, equipment_id>` cache to avoid
121127

122128
**`mul-import`** — Imports previously-fetched MUL data from local files into Postgres. Matches MUL units to DB units by slug (exact → normalized → case-insensitive full_name). Updates BV, cost, intro year, role, and MUL ID. Optionally imports faction/era availability from detail pages. Auto-creates new factions discovered in MUL. Outputs `unmatched_mul_units.csv` for manual review.
123129

130+
**`equipment-seed`** — Reads `data/equipment_stats.json` and updates equipment rows by slug match. Uses a static alias map to bridge clean JSON slugs (e.g. `clan-er-large-laser`) to MegaMek-generated DB slugs (e.g. `clerlargelaser`). Without `--force`, only updates NULL columns; with `--force`, overwrites all. Sets `stats_source = 'seed'` and `stats_updated_at = now()`.
131+
124132
**MUL module structure** (`mul/`):
125133
- `client.rs` — HTTP client with retry (429/5xx), jitter, configurable base URL
126134
- `quicklist.rs` — JSON deserialization for MUL QuickList endpoint
@@ -134,6 +142,10 @@ The scraper maintains an in-process `HashMap<slug, equipment_id>` cache to avoid
134142

135143
Schema is in `migrations/`. PostgreSQL enums defined: `tech_base_enum`, `rules_level_enum`, `equipment_category_enum`, `location_name_enum`.
136144

145+
**Construction reference tables** (migrations 6 + 8–10): `engine_types`, `armor_types`, `structure_types`, `heatsink_types`, `gyro_types`, `cockpit_types`, `myomer_types` store prescriptive component data for unit builders (weight multipliers, crit slots, rules levels). `engine_weight_table` maps engine ratings to standard weights. `mech_internal_structure` maps mech tonnage to per-location structure points. Seven `*_type_aliases` tables map MegaMek text strings to FK IDs. `unit_mech_data` has FK columns (`engine_type_id`, `armor_type_id`, etc.) linking to reference tables, populated on import via alias resolution. Standard gyro/cockpit/myomer are defaulted when MegaMek omits the field.
146+
147+
**Equipment builder columns** (migration 7): `equipment.observed_locations` (TEXT[], GIN-indexed) tracks which locations equipment appears in across existing units. `equipment.ammo_for_id` links ammo to its parent weapon. `equipment.stats_source` / `stats_updated_at` track provenance.
148+
137149
`tonnage` columns are `NUMERIC(10,1)` (widened in migration 2 from `NUMERIC(6,1)` to accommodate dropships/jumpships up to ~500,000 tons).
138150

139151
Unit slugs are lowercased, non-alphanumeric characters replaced with hyphens and deduplicated (e.g. `"Atlas AS7-D"``"atlas-as7-d"`). Chassis slugs include the unit type suffix to avoid collisions between different unit types sharing a name (e.g. `"atlas-mech"`, `"demolisher-vehicle"`). Unit slugs are derived from `"chassis model"`.

README.md

Lines changed: 76 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ A GraphQL API serving BattleTech unit, equipment, faction, and era data sourced
55
## Stack
66

77
- **API:** Rust · axum 0.8 · async-graphql 7 · sqlx 0.8 · PostgreSQL 16
8-
- **Scraper:** imports from MegaMek unit files (MTF + BLK formats) and the Master Unit List (BV, roles, availability, clan names)
8+
- **Scraper:** imports from MegaMek unit files (MTF + BLK formats), the Master Unit List (BV, roles, availability, clan names), and equipment stats seed data
99
- **Ops:** Prometheus metrics at `/metrics`, Dockerfile (musl/Alpine), IP rate limiting
1010

1111
## Quick start
@@ -112,7 +112,7 @@ The server starts on `http://localhost:8080`. In debug builds, GraphiQL is avail
112112
}
113113
}
114114

115-
# Single unit with full detail
115+
# Single unit with full detail and resolved component types
116116
{
117117
unit(slug: "atlas-as7-d") {
118118
fullName
@@ -126,17 +126,21 @@ The server starts on `http://localhost:8080`. In debug builds, GraphiQL is avail
126126
config
127127
isOmnimech
128128
engineRating
129-
engineType
130129
walkMp
131130
runMp
132131
jumpMp
133132
heatSinkCount
134-
heatSinkType
135-
armorType
136-
structureType
137-
gyroType
138-
cockpitType
139-
myomerType
133+
# Resolved component types with construction properties
134+
engine { name weightMultiplier ctCrits stCrits }
135+
armor { name pointsPerTon crits }
136+
structure { name weightFraction crits }
137+
heatsink { name dissipation crits weight }
138+
gyro { name weightMultiplier crits }
139+
cockpit { name weight crits }
140+
myomer { name }
141+
# Raw MegaMek strings (always available as fallback)
142+
engineTypeRaw
143+
armorTypeRaw
140144
}
141145
loadout {
142146
equipmentName
@@ -214,6 +218,47 @@ The server starts on `http://localhost:8080`. In debug builds, GraphiQL is avail
214218
}
215219
}
216220
}
221+
222+
# Equipment with stats and ammo relationships
223+
{
224+
equipment(slug: "autocannon-10") {
225+
name
226+
tonnage
227+
crits
228+
damage
229+
heat
230+
rangeShort
231+
rangeMedium
232+
rangeLong
233+
bv
234+
observedLocations
235+
ammoTypes { slug name }
236+
}
237+
}
238+
239+
# Equipment search with builder filters
240+
{
241+
allEquipment(maxTonnage: 2.0, maxCrits: 3, observedLocation: "right_arm") {
242+
edges {
243+
node { slug name tonnage crits }
244+
}
245+
}
246+
}
247+
248+
# Construction reference — all component types in one request
249+
{
250+
constructionReference {
251+
engineTypes { slug name techBase weightMultiplier ctCrits stCrits }
252+
armorTypes { slug name pointsPerTon crits }
253+
structureTypes { slug name weightFraction crits }
254+
heatsinkTypes { slug name dissipation crits weight }
255+
gyroTypes { slug name weightMultiplier crits }
256+
cockpitTypes { slug name weight crits }
257+
myomerTypes { slug name }
258+
engineWeights { rating standardWeight }
259+
internalStructure { tonnage head centerTorso sideTorso arm leg }
260+
}
261+
}
217262
```
218263

219264
### Filters
@@ -234,6 +279,19 @@ The `units` query supports the following filters:
234279
| `hasJump` | Bool | Jump-capable mechs only |
235280
| `role` | String | Tactical role (e.g. `"Juggernaut"`, `"Sniper"`, `"Striker"`) |
236281

282+
The `allEquipment` query supports additional builder-oriented filters:
283+
284+
| Filter | Type | Description |
285+
|--------|------|-------------|
286+
| `nameSearch` | String | Case-insensitive substring match on equipment name |
287+
| `category` | String | Equipment category in snake_case (e.g. `"energy_weapon"`) |
288+
| `techBase` | String | `inner_sphere`, `clan`, `mixed`, `primitive` |
289+
| `rulesLevel` | String | `introductory`, `standard`, `advanced`, `experimental`, `unofficial` |
290+
| `maxTonnage` | Float | Equipment weighing at most this many tons |
291+
| `maxCrits` | Int | Equipment consuming at most this many critical slots |
292+
| `observedLocation` | String | Equipment observed at this location (e.g. `"right_arm"`) |
293+
| `ammoForSlug` | ID | Ammo types compatible with this weapon slug |
294+
237295
### Limits
238296

239297
- Query depth: 20
@@ -343,3 +401,12 @@ All imports are idempotent — inserts use `ON CONFLICT ... DO UPDATE`.
343401
| `unit_availability` | ~100,000+ | MUL |
344402
| `eras` | 10 | seed + MUL |
345403
| `factions` | ~70 | seed + MUL |
404+
| `engine_types` | 9 | construction ref |
405+
| `armor_types` | 9 | construction ref |
406+
| `structure_types` | 6 | construction ref |
407+
| `heatsink_types` | 4 | construction ref |
408+
| `gyro_types` | 5 | construction ref |
409+
| `cockpit_types` | 6 | construction ref |
410+
| `myomer_types` | 4 | construction ref |
411+
| `engine_weight_table` | 79 | construction ref |
412+
| `mech_internal_structure` | 17 | construction ref |

crates/api/src/handlers/llms_txt.rs

Lines changed: 94 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ pub fn generate_llms_txt(base_url: &str) -> String {
22
format!(
33
r#"# BattleTech Data API
44
5-
> GraphQL API for BattleTech tabletop game data: units (mechs, vehicles, fighters, dropships), equipment (weapons, armor, engines), factions, and eras. Data sourced from MegaMek 0.50.11 (~6,500 units, ~2,875 equipment items) enriched with Master Unit List (MUL) data (BV, roles, availability).
5+
> GraphQL API for BattleTech tabletop game data: units (mechs, vehicles, fighters, dropships), equipment (weapons, armor, engines), construction reference tables, factions, and eras. Data sourced from MegaMek 0.50.11 (~6,500 units, ~2,875 equipment items) enriched with Master Unit List (MUL) data (BV, roles, availability). Includes construction reference data for unit builders (engine/armor/structure/heatsink/gyro/cockpit/myomer types with weights and crit slots).
66
77
## Endpoint
88
@@ -29,6 +29,8 @@ GET {base_url}/schema.graphql
2929
- **Tonnage**: weight in metric tons (20–100 for mechs, up to 500,000+ for jumpships)
3030
- **Range values**: measured in tabletop hexes
3131
- **Crits**: number of critical hit slots an equipment item occupies
32+
- **Resolved component types**: `mechData` provides both raw MegaMek strings (e.g. `engineTypeRaw`) and resolved references (e.g. `engine`) with full construction properties (weight multipliers, crit slots, etc.)
33+
- **Construction reference**: prescriptive data for unit builders — component types with weights, crit slots, and rules; engine weight table; internal structure table
3234
3335
## Pagination
3436
@@ -85,7 +87,7 @@ To paginate: pass `endCursor` from the previous response as `after` in the next
8587
}}
8688
```
8789
88-
### Get a single unit with full loadout, armor, and mech data
90+
### Get a single unit with full loadout, armor, and resolved component types
8991
```graphql
9092
{{
9193
unit(slug: "atlas-as7-d") {{
@@ -101,17 +103,24 @@ To paginate: pass `endCursor` from the previous response as `after` in the next
101103
config
102104
isOmnimech
103105
engineRating
104-
engineType
105106
walkMp
106107
runMp
107108
jumpMp
108109
heatSinkCount
109-
heatSinkType
110-
structureType
111-
armorType
112-
gyroType
113-
cockpitType
114-
myomerType
110+
engine {{ name weightMultiplier ctCrits stCrits }}
111+
armor {{ name pointsPerTon crits }}
112+
structure {{ name weightFraction crits }}
113+
heatsink {{ name dissipation crits weight }}
114+
gyro {{ name weightMultiplier crits }}
115+
cockpit {{ name weight crits }}
116+
myomer {{ name properties }}
117+
engineTypeRaw
118+
armorTypeRaw
119+
structureTypeRaw
120+
heatSinkTypeRaw
121+
gyroTypeRaw
122+
cockpitTypeRaw
123+
myomerTypeRaw
115124
}}
116125
loadout {{
117126
equipmentName
@@ -166,7 +175,7 @@ To paginate: pass `endCursor` from the previous response as `after` in the next
166175
tonnage
167176
mechData {{
168177
config
169-
engineType
178+
engine {{ name }}
170179
walkMp
171180
runMp
172181
jumpMp
@@ -231,7 +240,7 @@ To paginate: pass `endCursor` from the previous response as `after` in the next
231240
}}
232241
```
233242
234-
### Search equipment
243+
### Search equipment with builder filters
235244
```graphql
236245
{{
237246
allEquipment(first: 10, nameSearch: "laser", category: "energy_weapon", techBase: "clan") {{
@@ -240,12 +249,14 @@ To paginate: pass `endCursor` from the previous response as `after` in the next
240249
slug
241250
name
242251
tonnage
252+
crits
243253
damage
244254
heat
245255
rangeShort
246256
rangeMedium
247257
rangeLong
248258
bv
259+
observedLocations
249260
}}
250261
}}
251262
pageInfo {{
@@ -255,6 +266,78 @@ To paginate: pass `endCursor` from the previous response as `after` in the next
255266
}}
256267
```
257268
269+
### Filter equipment by weight, crits, and location
270+
```graphql
271+
{{
272+
allEquipment(first: 20, maxTonnage: 2.0, maxCrits: 3, observedLocation: "right_arm") {{
273+
edges {{
274+
node {{
275+
slug
276+
name
277+
tonnage
278+
crits
279+
observedLocations
280+
}}
281+
}}
282+
}}
283+
}}
284+
```
285+
286+
### Find ammo types for a weapon
287+
```graphql
288+
{{
289+
equipment(slug: "autocannon-10") {{
290+
name
291+
ammoTypes {{
292+
slug
293+
name
294+
tonnage
295+
bv
296+
}}
297+
}}
298+
}}
299+
```
300+
301+
### Construction reference — fetch all data for builder initialization
302+
```graphql
303+
{{
304+
constructionReference {{
305+
engineTypes {{ slug name techBase weightMultiplier ctCrits stCrits }}
306+
armorTypes {{ slug name techBase pointsPerTon crits }}
307+
structureTypes {{ slug name techBase weightFraction crits }}
308+
heatsinkTypes {{ slug name techBase dissipation crits weight }}
309+
gyroTypes {{ slug name weightMultiplier crits }}
310+
cockpitTypes {{ slug name weight crits }}
311+
myomerTypes {{ slug name properties }}
312+
engineWeights {{ rating standardWeight }}
313+
internalStructure {{ tonnage head centerTorso sideTorso arm leg }}
314+
}}
315+
}}
316+
```
317+
318+
### Look up engine weight for a specific rating
319+
```graphql
320+
{{
321+
engineWeights(rating: 300) {{
322+
rating
323+
standardWeight
324+
}}
325+
}}
326+
```
327+
328+
### Look up internal structure for a tonnage
329+
```graphql
330+
{{
331+
internalStructure(tonnage: 75) {{
332+
head
333+
centerTorso
334+
sideTorso
335+
arm
336+
leg
337+
}}
338+
}}
339+
```
340+
258341
### List all Clan factions
259342
```graphql
260343
{{

0 commit comments

Comments
 (0)