# Project Catalogue — the command reference

> 📍 Part of the project catalogue doc set — the map and routing table is
> [start-here.md](/docs/modules_handbook/shared/project-catalogue/start-here.md).

Every artisan command that reads or writes catalogue-family data: **51 of them**, generated
from the `$signature` and `$description` of each class in `app/Console/Commands/` and grouped
by what it is FOR. Half of them were named in no document before this file existed.

Three things to know before running any of them:

- ⚠️ **`php artisan list catalogue` does not show you all of these.** It filters by namespace,
  so it misses `catalog:estimate-key-sizes` (singular), `market:*`, `developers:*`,
  `layouts:project`, `edgeprop:import` and `petav2:*`. The table below is the complete set.
- **The class doc-blocks are the real documentation.** Most explain the incident or the
  constraint the command was written for, in far more detail than a one-line description can.
  Open the file named in the table before running anything destructive.
- **Which database a command hits depends on `CATALOGUE_USE_DEFAULT_CONNECTION`.** Read
  [databases-and-distribution.md](/docs/modules_handbook/shared/project-catalogue/databases-and-distribution.md)
  §2 first. Several of these write to whatever the `catalogue` connection currently points at,
  and only one (`catalogue:copy-uae-snapshots`) refuses to run if that is ambiguous.

---

## Ingestion — getting provider data in

| Command | Class | What it does |
|---|---|---|
| `catalogue:sync` | `SyncCatalogue` | Ingest one provider into the canonical project catalogue. **The only catalogue command in the scheduler — and it ships DISABLED** |
| `edgeprop:import` | `ImportEdgepropData` | Import EdgeProp scraped JSON into the catalogue via the source resolver |
| `catalogue:scrape-edgeprop` | `ScrapeEdgepropNewLaunches` | Crawl EdgeProp.my new launches into a catalogue inbox artifact |
| `catalogue:scrape-iproperty` | `ScrapeIpropertyNewLaunches` | Crawl iProperty.com.my new launches into a catalogue inbox artifact |
| `catalogue:harvest-uae-details` | `HarvestUaeDetails` | Harvest Estate Real-Time UAE API payloads into catalogue source snapshots |
| `catalogue:hk-map-sources` | `MapHkSources` | Report (and optionally apply) deterministic HK cross-provider source mappings |
| `catalogue:my-map-sources` | `MapMySources` | Report (and optionally apply) MY new-launch cross-provider source mappings |

The scrapers are **manual and credit-gated** — each crawl costs money. See the module doc's
*Merge* and *Scraped review* sections for what happens to an artifact after it lands.

## Market reference data

| Command | Class | What it does |
|---|---|---|
| `market:import` | `ImportMarketData` | Copy market reference tables from petav2 into petav3 in batches (no mysqldump). Reads `edgeprop_projects`, `airbnbs`, `propsense_agents` over the `reference` connection |
| `market:import-hk` | `ImportHkMarketReference` | Import the HK market reference layer into the `market_*` tables |

## Publication

| Command | Class | What it does |
|---|---|---|
| `catalogue:completeness` | `ReportCatalogueCompleteness` | Report per-market completeness against the property-detail contract |
| `catalogue:propertysifu-publish-complete` | `PublishPropertySifuComplete` | Publish complete MY rows backed by an active PropertySifu source |
| `catalogue:hk-publish-parity` | `PublishHkParity` | Publish HK rows backed by an active House730 source (reference-site parity) |
| `catalogue:ae-publish-parity` | `PublishAeParity` | Publish UAE rows backed by an active PropertyFinder source with a hero image |
| `catalogue:unpublish` | `UnpublishCatalogue` | Unpublish records in bulk, reversibly (writes a rollback file) |

⚠️ **The two `*-publish-parity` commands bypass the completeness gate and the review entirely** —
they call `publishMany()`, a bare `update(['published_at' => now()])`. A published row is
therefore not proof that `publishIfComplete()` ever passed for it. See the module doc's
*Publication* section.

## Derived and repaired fields

| Command | Class | What it does |
|---|---|---|
| `catalogue:normalise-classification` | `NormaliseCatalogueClassification` | Fold tenure / sale status / property type onto their canonical vocabularies |
| `catalogue:derive-sale-status` | `DeriveCatalogueSaleStatus` | Recompute `sale_status` from the completion year |
| `catalogue:derive-price-range` | `DeriveLaunchPriceRange` | Recompute `price_min` / `price_max` from priced floor plans |
| `catalogue:backfill-completion-year` | `BackfillCompletionYear` | Ask Gemini for the completion year of high-rises whose year is unknown |
| `catalogue:search-completion-year` | `SearchCompletionYear` | Search the web for completion years and write the corroborated ones |
| `catalogue:pin-completion-years` | `PinCompletionYears` | Pin AI-backfilled years so `catalogue:sync` stops reverting them to feed values |
| `catalogue:hk-backfill-regions` | `BackfillHkRegions` | Backfill the HK region (state) from the district |
| `catalogue:hk-clear-stale-prices` | `ClearStaleHkLaunchPrices` | Clear HK launch prices left by the retired plan-derived range |

## Floor plans and analysis

| Command | Class | What it does |
|---|---|---|
| `catalogue:merge-duplicate-layouts` | `MergeDuplicateFloorPlans` | Merge sizeless duplicate plans into their sized twin, moving media and filling blanks |
| `catalog:estimate-key-sizes` | `EstimateKeySizes` | Estimate each dual-key layout's per-key floor area from its plan drawing (note: `catalog:`, singular) |
| `catalogue:precompute-analysis` | `PrecomputeCatalogueAnalysis` | Precompute and store the unit analysis for every analysable layout. **Not scheduled** |
| `layouts:project` | `ProjectLayoutAnalyses` | Flatten saved unit analyses into the `layout_analyses` read model |

## Developers

| Command | Class | What it does |
|---|---|---|
| `developers:consolidate` | `ConsolidateDevelopers` | Merge duplicate developers into one company each, linking subsidiaries and joint developers beneath |
| `developers:restore` | `RestoreDeveloperConsolidation` | Undo a `developers:consolidate` run from its rollback file |
| `catalogue:plan-developer-names` | `PlanCatalogueDeveloperNames` | Build an ID-keyed developer-name plan from the reviewed mapping CSV |
| `catalogue:apply-developer-names` | `ApplyCatalogueDeveloperNames` | Apply reviewed developer names, matched by catalogue ID |
| `catalogue:repair-developer-pins` | `RepairDeveloperPins` | Re-point developer pivots that drifted away from their pin |

## Media and slugs (country rollout)

| Command | Class | What it does |
|---|---|---|
| `catalogue:store-house730-media` | `StoreHouse730CatalogueMedia` | Store House730 assets on the configured media disk |
| `catalogue:store-uae-media` | `StoreUaeCatalogueMedia` | Store PropertyFinder UAE assets on the configured media disk |
| `catalogue:upload-uae-media-objects` | `UploadUaeMediaObjects` | Upload locally-stored UAE media files to GCS at their existing paths |
| `catalogue:hk-apply-slugs` | `ApplyHkCatalogueSlugs` | Adopt the House730 feed slug as the HK catalogue slug |
| `catalogue:ae-apply-slugs` | `ApplyAeCatalogueSlugs` | Adopt the flattened PropertyFinder feed slug as the UAE catalogue slug |

⚠️ The two `store-*-media` commands write through `CatalogMedia` / `MasterMedia`, i.e. into
whatever the `catalogue` connection points at, and neither checks the flag. Check it yourself.

## Moving a catalogue between deployments

| Command | Class | What it does |
|---|---|---|
| `catalogue:export` | `ExportProjectCatalogue` | Write a versioned JSON package. **Aborts without writing** if any media / AI row cannot be represented, unless `--skip-orphans` |
| `catalogue:import` | `ImportProjectCatalogue` | Import a package from another deployment. Idempotent, one transaction |
| `catalogue:verify-sync` | `VerifyCatalogueSync` | Prove a destination matches a shipped package, field by field (read-only) |
| `catalogue:explain-drift` | `ExplainCatalogueDrift` | Explain WHY verify-sync reported drift, local row beside the package (read-only) |
| `catalogue:export-csv` | `ExportCatalogueCsv` | Export the New Project and Subsale databases as two CSV files |

## The shared master (lifecycle)

| Command | Class | What it does |
|---|---|---|
| `catalogue:cutover` | `CutoverToMasterCatalogue` | Mark local rows as mirrors and clear the local id space, after pointing at the master |
| `catalogue:offset-local-ids` | `OffsetLocalCatalogueIds` | Start this platform's own ids above the master's id space |
| `catalogue:mirror-from-master` | `MirrorCatalogueFromMaster` | Refresh this deployment's COPY from the master. ⚠️ **Deletes every local-only row except in `media`** |
| `catalogue:repair-refs` | `RepairCatalogueReferences` | Rebuild site tables' catalogue integer keys from their stored uuids. ⚠️ Repairs only the 10 tables in its own map |
| `catalogue:copy-uae-snapshots` | `CopyUaeSnapshotsToMaster` | Copy UAE analysis snapshot payloads from local to the master. **The only command that refuses to run when the target is ambiguous** |

## Legacy retirement

| Command | Class | What it does |
|---|---|---|
| `catalogue:backfill-redesign` | `BackfillRedesign` | Backfill legacy Analyze data into the redesigned schema (idempotent) |
| `catalogue:apply-identity-decisions` | `ApplyIdentityDecisions` | Apply owner-reviewed identity decisions for ambiguous legacy properties (all-or-nothing) |
| `catalogue:audit-redesign` | `AuditRedesign` | **The drop gate.** Reconcile the backfill and prove zero legacy runtime references (non-zero exit on any failure) |
| `petav2:retire-subsale` | `RetirePetav2Subsale` | Hide, restore or delete the petaV2 legacy subsale catalogue sources |
| `petav2:import-all` | `ImportPetav2All` | Run all petaV2 → petav3 imports (calls, F2F recordings, wealth plans) |

See [neighbours-and-legacy.md](/docs/modules_handbook/shared/project-catalogue/neighbours-and-legacy.md)
§2 for the eight retired tables these operate on, and why they must not be dropped yet.

---

## Keeping this file honest

It was generated by reading `$signature` and `$description` off every class in
`app/Console/Commands/`. When you add, rename or delete a catalogue command, update the row
here in the same change — and if a command gains a behaviour worth warning about, put the
warning beside its row rather than only in the doc-block.
