key: build_a_directory
recipe:
  when: "programmatic SEO \u2014 many per-city pages listing businesses in a category\
    \ (e.g. 'plumbers in {city}')"
  preferred_path: 'Two concepts: CATEGORIES (top-level verticals with SEO templates)
    + LISTINGS (individual businesses inside them, grouped by city). Create a category,
    bulk-import listings from an IDAP dump or SpiderMaps job, and the platform auto-generates
    /directory/{category}/ + /directory/{category}/{city}/ + /directory/{category}/{city}/{listing}
    pages with SEO title/description rendered from your templates.'
  steps:
  - directory_create_category(name='Plumbers', slug='plumbers', seo_title_template='Best
    {category} in {city} | Acme Directory', seo_description_template='Find top-rated
    {category} in {city}. Compare ratings, reviews, and contact info.')
  - '# Then import listings, ideally from IDAP/SpiderMaps results:'
  - 'directory_bulk_upsert_listings(category_slug=''plumbers'', listings=[{name, slug?,
    city, state?, phone?, website?, rating?, review_count?, data?: {hours: [...]}},
    ...])'
  - "# No publish step \u2014 listings default to status='published'. No deploy step\
    \ \u2014 pages render live."
  - "# Verify: curl /content/directory/categories/plumbers \u2192 list of cities with\
    \ listing counts"
  seo_templates:
    placeholders: "{category}, {city}, {listing} \u2014 rendered server-side on every\
      \ directory page"
    example_title: Best {category} in {city} | Acme
    example_description: Compare {category} in {city}. Ratings, reviews, hours, directions.
    sitemap: Every category, every (category,city), and every published listing gets
      a sitemap.xml entry automatically
  url_structure:
    category_hub: "/directory/{category_slug}                         \u2192 cities\
      \ list"
    city_page: "/directory/{category_slug}/{city_slug}             \u2192 listings\
      \ in that city"
    listing_page: "/directory/{category_slug}/{city_slug}/{listing_slug} \u2192 single\
      \ listing detail"
    city_slug: "LOWER(city + '-' + state), stripped of non-alphanumeric (e.g. 'Miami\
      \ Beach' + 'Florida' \u2192 'miami-beach-florida')"
  listing_fields:
    required:
    - name
    common:
    - slug
    - description
    - city
    - state
    - country
    - address
    - phone
    - email
    - website
    - rating
    - review_count
    - latitude
    - longitude
    flexible: "`data` JSONB \u2014 stick hours, amenities, images, anything the SEO\
      \ template needs"
    traceability: "`source_job_id` \u2014 UUID of the SpiderIQ job that produced this\
      \ listing"
  ecosystem_integration:
    idap_flow: "IDAP stores every business SpiderIQ has seen (SpiderMaps + SpiderSite\
      \ + SpiderCompanyData merged). A bulk_upsert_listings call can drop an entire\
      \ IDAP result set into a directory category \u2014 set source_job_id so you\
      \ can audit provenance."
    spidermaps_flow: "Run a SpiderMaps campaign \u2192 collect results \u2192 directory_bulk_upsert_listings(category_slug,\
      \ listings=results). For large imports, paginate at 5000 per call."
    merge_tags: "Listings use the same merge-tag pipeline as dynamic landing pages\
      \ \u2014 any field you store in data JSONB can be surfaced in a custom template."
  anti_patterns:
  - "DO NOT create a category per city \u2014 one category spans all cities. Cities\
    \ are derived from listings."
  - "DO NOT manually manage city_slug \u2014 the materialized view computes it from\
    \ city + state."
  - "DO NOT bulk-import more than 5000 listings in one call \u2014 paginate larger\
    \ imports to avoid txn timeouts."
  - "DO NOT bypass the bulk endpoint for IDAP dumps \u2014 individual upserts work\
    \ but burn 100\xD7 the API budget."
  see:
  - directory
  - merge_tags
  - sitemap
