一键导入
location-system
Countries, states, cities, ResolveLocationAction, Location base model, LocationType enum, geocoding, and location-level Redis data.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Countries, states, cities, ResolveLocationAction, Location base model, LocationType enum, geocoding, and location-level Redis data.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Photo upload, tagging, verification status, summary generation, XP calculation, AddTagsToPhotoAction, UploadPhotoController, and the VerificationStatus enum.
OpenLitterMap v5 architecture reference. Use this skill whenever working on OLM backend code, Laravel controllers, services, events, tests, Redis, metrics, tags, photos, teams, admin, leaderboards, or any part of the OpenLitterMap codebase. Also trigger when the user mentions MetricsService, VerificationStatus, PhotoTags, AddTagsToPhotoAction, TagsVerifiedByAdmin, rewardXpToAdmin, school pipeline, teacher approval, clustering, or any OLM-specific concept. This skill should be used BEFORE writing any OLM code to avoid architectural mistakes. When in doubt about OLM patterns, read this skill.
ProfileController, ApiSettingsController, DeleteAccountController, user stats (Redis + MySQL fallback), LevelService, privacy toggles, account deletion with Redis cleanup, and the Profile Vue page.
REST API endpoints, route structure, auth guards, request/response contracts, error patterns, and the full API surface for web SPA and mobile clients.
PhotoTag, PhotoTagExtraTags, categories, litter objects, materials, brands, ClassifyTagsService, GeneratePhotoSummaryService, tag migration, and the v4-to-v5 conversion.
The olm:v5 migration script, UpdateTagsService, batch processing, migrated_at, ClassifyTagsService deprecated mappings, and data migration from v4 category tables.
| name | location-system |
| description | Countries, states, cities, ResolveLocationAction, Location base model, LocationType enum, geocoding, and location-level Redis data. |
Location tables store identity only (name, shortcode, FKs). All aggregates live in the metrics table and Redis. The Location base model computes all stats from Redis on access via $appends.
app/Models/Location/Location.php — Abstract base model with Redis-backed computed attributesapp/Models/Location/Country.php — Route key: shortcode (ISO 3166-1 alpha-2)app/Models/Location/State.php — Belongs to Countryapp/Models/Location/City.php — Belongs to Country + Stateapp/Actions/Locations/ResolveLocationAction.php — Lat/lon -> Country/State/City via geocodingapp/Actions/Locations/ReverseGeocodeLocationAction.php — LocationIQ API wrapperapp/Actions/Locations/LocationResult.php — DTO returned by ResolveLocationActionapp/Enums/LocationType.php — Global(0), Country(1), State(2), City(3)app/Enums/Timescale.php — AllTime(0), Daily(1), Weekly(2), Monthly(3), Yearly(4)total_* counters, no manual_verify, no aggregates. All stats come from Redis or the metrics table.country_id, state_id, city_id. Deprecated string columns (country, county, city, display_name, location, road) are dropped.metrics table.PFCOUNT gives ~0.81% error, O(1) space, append-only (cannot decrement).shortcode as route key, not id. Routes: /countries/{shortcode}.// app/Actions/Locations/ResolveLocationAction.php
public function run(float $lat, float $lon): LocationResult
{
$address = $this->reverseGeocode->run($lat, $lon);
$country = $this->resolveCountry($address); // firstOrCreate by country_code
$state = $this->resolveState($country, $address);
$city = $this->resolveCity($country, $state, $address);
return new LocationResult($country, $state, $city, $address, $displayName);
}
Lookup strategy for city: Searches keys in order: city, town, city_district, village, hamlet, locality, county.
readonly class LocationResult
{
public function __construct(
public Country $country,
public State $state,
public City $city,
public array $addressArray,
public string $displayName,
) {}
}
enum LocationType: int
{
case Global = 0; // dbColumn: null, scopePrefix: {g}
case Country = 1; // dbColumn: country_id, scopePrefix: {c:$id}
case State = 2; // dbColumn: state_id, scopePrefix: {s:$id}
case City = 3; // dbColumn: city_id, scopePrefix: {ci:$id}
public function dbColumn(): ?string
public function scopePrefix(int $id = 0): string
public function modelClass(): ?string
public function parentType(): ?self
}
// All appended attributes on Country/State/City models:
$country->total_litter_redis // HGET {c:$id}:stats litter
$country->total_photos_redis // HGET {c:$id}:stats uploads
$country->total_contributors_redis // PFCOUNT {c:$id}:hll
$country->total_xp // HGET {c:$id}:stats xp
$country->litter_data // HGETALL {c:$id}:cat (resolved to names)
$country->objects_data // top 20 from {c:$id}:obj
$country->materials_data // HGETALL {c:$id}:mat
$country->brands_data // HGETALL {c:$id}:brands
$country->ppm // Cached time-series from metrics table (15min TTL)
$country->recent_activity // Last 7 days daily counts (5min TTL)
RedisKeys::globalCountryLitterRanking() // {g}:rank:c:litter (ZSET)
RedisKeys::globalCountryPhotosRanking() // {g}:rank:c:photos
RedisKeys::countryStateRanking($countryId, $metric) // {c:$id}:rank:s:$metric
RedisKeys::stateCityRanking($stateId, $metric) // {s:$id}:rank:ci:$metric
countries (id, country, shortcode UNIQUE, created_by, timestamps)
states (id, state, country_id, created_by, timestamps, UNIQUE(country_id, state))
cities (id, city, country_id, state_id, created_by, timestamps, UNIQUE(country_id, state_id, city))
app/Http/Controllers/Location/LocationController.php serves the locations browsing UI.
GET /api/v1/locations — Global view: list of countries with statsGET /api/v1/locations/{type}/{id} — Drill into country/state/city{
"stats": { "countries": 120, "photos": 50000, "tags": 150000, ... },
"locations": [
{
"id": 1, "name": "Ireland", "shortcode": "IE",
"total_tags": 5000, "total_images": 1200, "total_members": 45,
"xp": 15000, "created_at": "...", "updated_at": "...",
"pct_tags": 3.3, "pct_photos": 2.4, "avg_tags_per_person": 111.1
}
],
"location_type": "country",
"breadcrumbs": [ ... ],
"activity": { "today": { ... }, "this_month": { ... } }
}
Key naming: Response uses locations (not children) and location_type (not children_type). Children use total_tags, total_images, total_members (not tags, photos, contributors). The Pinia store useLocationsStore reads these exact keys.
Supports ?period=today|yesterday|this_month|last_month|this_year and ?year=2024 query params. Mutually exclusive — year clears period and vice versa.
metrics table and Redis. Location tables are identity only.country, county, city, display_name, location, road are dropped. Use country_id, state_id, city_id FKs.getRouteKeyName(): 'shortcode'.metrics table is source of truth.GeocodingException. ResolveLocationAction::run() throws GeocodingException when geocoding fails. Always handle this.children or children_type in API responses. The correct keys are locations and location_type.manual_verify. This deprecated column is no longer used. Don't scope queries with it.