# Add Sample Data

> Use when the user wants to seed Dataverse tables with realistic sample records so a freshly-scaffolded code app shows real-looking data on first launch. Generates contextually appropriate rows from each table's schema and inserts them in dependency order. Mirrors microsoft/power-platform-skills/power-pages/add-sample-data, adapted for mobile apps.

- **Type:** Skill
- **Install:** `agentstack add skill-leonardseo-power-platform-skills-codex-add-sample-data`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [LeonardSEO](https://agentstack.voostack.com/s/leonardseo)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [LeonardSEO](https://github.com/LeonardSEO)
- **Source:** https://github.com/LeonardSEO/power-platform-skills-codex/tree/main/plugins/mobile-app/skills/add-sample-data

## Install

```sh
agentstack add skill-leonardseo-power-platform-skills-codex-add-sample-data
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

**📋 Shared instructions: [shared-instructions.md](${CLAUDE_SKILL_DIR}/../../shared/shared-instructions.md)** — read first.

# Add Sample Data

Populate Dataverse tables with realistic sample records so a freshly-scaffolded code app shows real-looking data on first launch. Generates rows from each table's schema and inserts them in dependency order. Use after `/add-dataverse` (or `/setup-datamodel`) has created the tables.

## Core principles

- **Coverage over volume — every table in the manifest gets seeded.** The #1 failure mode of a freshly-scaffolded code app is a home / dashboard / list screen that renders an empty state on first launch because its source table has zero rows. An empty downstream table is **worse than a 3-row table.** Default to minimal-but-complete: small counts everywhere, no table left empty. Volume is a secondary knob — coverage is the contract.
- **Insertion order matters.** Parent / referenced tables must be inserted before child / referencing tables so lookup IDs are available.
- **Contextual data, not Lorem Ipsum.** Generate values that match column names + types. A `cr3e9_sitename` column in an inspection app gets "Westside Construction Site", not "Sample Name 1".
- **Scenario-aware rows.** Read `native-app-plan.md`, especially `### Shared Conventions` and per-screen `Operational pattern` values defined in [screen-templates.md](${CLAUDE_SKILL_DIR}/../../shared/references/screen-templates.md). Seed rows should exercise the app's actual workflow: statuses, dates, relationships, priority/severity, media metadata, and edge cases that make the planned first viewport light up.
- **Fail gracefully.** On insertion failure, log the error and continue with remaining records — never auto-rollback. The user can re-run after fixing the issue.
- **Idempotent re-runs.** If a previous run partially completed, the second run reads `memory-bank.md`'s seeded-data table and skips records already inserted.
- **Solution-scoped inserts.** Always pass `--solution ` so records land in our solution, not the default.

## Workflow

1. Verify project + auth → 2. Discover tables → 3. Select tables + count → 4. Generate + preview → 5. Insert → 6. Summary

## Prototype Seed Reuse

`--from-seed` is used by `/prototype-to-real-app` after a mock prototype is converted to Dataverse. In this mode, prefer existing prototype seed files before generating new rows:

```text
src/generated/services/*/*.seed.json
src/generated/services/*.seed.json
```

Map seed objects to Dataverse payloads using `.datamodel-manifest.json`:

- Keep values only for real manifest columns.
- Translate lookup references into exact `@odata.bind` keys from the manifest.
- Keep picklist integers from the manifest; do not invent values from labels.
- Skip local-only prototype fields that have no Dataverse column.
- Preserve dependency-tier insertion order.

If a seed file cannot be mapped safely, fall back to generated contextual sample rows for that table and record `DONE_WITH_CONCERNS` in the summary. `--from-seed` is a preference, not permission to insert malformed data.

---

### Step 1 — Verify project & auth

```bash
test -f power.config.json && test -f app.config.js
node "${CLAUDE_SKILL_DIR}/../../scripts/resolve-environment.js" "$(node -e \"console.log(require('./power.config.json').environmentId)\")"
```

Capture the **environment URL** for subsequent script calls. If resolution fails, instruct `az login --tenant ` or ask for the environment URL directly, then stop.

Verify Azure CLI auth (the script needs an Azure CLI token):

```bash
az account show --query "user.name" -o tsv
```

If empty, instruct `az login` and stop.

### Step 2 — Discover tables

#### Step 2a — Path A: read `.datamodel-manifest.json` (preferred)

```bash
test -f .datamodel-manifest.json
```

If present, parse the JSON. It already contains `logicalName`, `displayName`, `status` (`new` / `extended` / `reused`), and `columns` for every table the project uses. **This is the preferred path** — fast, no API calls.

```bash
cat .datamodel-manifest.json | jq '.tables[] | { logicalName, displayName, columnCount: (.columns | length) }'
```

Skip Step 2b.

#### Step 2b — Path B: query OData (fallback)

If `.datamodel-manifest.json` is missing, discover custom tables via the script:

```bash
node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js"  GET \
  "EntityDefinitions?\$select=LogicalName,DisplayName,EntitySetName&\$filter=IsCustomEntity eq true"
```

For each table the project uses, fetch its custom columns:

```bash
node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js"  GET \
  "EntityDefinitions(LogicalName='')/Attributes?\$select=LogicalName,DisplayName,AttributeType,RequiredLevel&\$filter=IsCustomAttribute eq true"
```

Build the same `{ logicalName, displayName, columns: [...] }` shape the manifest provides.

### Step 3 — Select tables + count

All tables from the manifest are evaluated — including reused ones — because a mobile app that surfaces data from a shared table still needs rows to render on first launch. The only exception is standard system tables (e.g. `contact`, `account`, `systemuser`) where seeding is risky in shared production environments.

**Pre-seeding row-count check (HARD — runs for every table before generating any rows):**

For each table, query its current record count using the entity set name from the manifest (or derive it by appending `s` to the logical name as a fallback):

```bash
node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js"  GET \
  "?\$top=5&\$select="
```

Count the rows returned in the `value` array.

| Existing record count | Action |
|---|---|
| **≥5** | **Skip this table entirely.** Log: `↷  (≥5 records exist, skipping)`. Do not generate or insert any rows. |
| ** `→ Seeding  records into  tables (coverage-first; counts auto-tuned per class).`

#### Step 3b — Determine insertion order

For the selected tables, build a dependency graph from lookup columns:

1. Tables with no lookups out → Tier 0 (insert first)
2. Tables with lookups only to Tier 0 → Tier 1
3. Continue until all selected tables are tiered

If a selected table references an UNSELECTED parent, ask the user whether to add the parent to the selection or skip the lookup field. Don't silently insert null lookups.

### Step 4 — Generate sample data + preview

#### Step 4a — Generate contextual rows

For each selected table, generate N rows. Match values to column names + types:

| Column type | Generation approach |
|---|---|
| **String** | Match the column name's semantic. `*name`, `*title` → realistic names from the requirements brief context. `*email` → `firstname.lastname@example.com`. `*phone` → `(555) 123-NNNN`. `*address` → realistic street + city. Otherwise: short context-appropriate text. |
| **Memo (multi-line text)** | 1-3 sentences relevant to the column name (e.g. `*notes`, `*description`). |
| **Integer / Decimal / Currency** | Reasonable range based on column name. `*amount`, `*price` → realistic dollars. `*count`, `*quantity` → small integers (1-100). |
| **DateTime / DateOnly** | Recent ISO dates spanning past 30 days to next 14 days. Vary across rows. |
| **Boolean** | Mix true/false (~70/30 favoring true for `is_active` style names). |
| **Choice (Picklist)** | **Query options first** (Step 4b), then pick from valid integer values. |
| **MultiSelect Choice** | Pick 1-3 valid values per row from the option set. |
| **Lookup** | Reference a record from the parent table that was (or will be) inserted in this run. Track parent GUIDs from Step 5's POST responses. |
| **Image / File** | Default: skip — leave null. If media seeding is enabled and the column is business data (product image, inspection evidence, NC proof), use generated/synthetic local files from `assets/sample-*` and record provenance. Never upload decorative UI hero assets to Dataverse. |

**Media seeding policy (business data only, only if needed):**

- Default: do not seed binary media. Seed metadata rows and leave Image/File columns null unless the screen plan or user request requires visible sample media.
- Seed Dataverse images/files only when the image belongs to a record users inspect in list/detail screens: product photos, evidence, attachments, signatures, issue proof. Do NOT seed Home hero, splash, app icon, empty-state art, or decorative detail backgrounds.
- Prefer generated/synthetic assets with no logos, no real product labels, no faces, no watermarks, and no competitor branding. If the user supplies approved assets, use those and record their source.
- CDN URLs are valid only for explicit URL/Text columns (e.g. `imageurl`, `photourl`). Do not put CDN URLs into File/Image columns.
- Dataverse Image columns receive base64 in the row payload or generated service shape. Dataverse File columns require a second upload step after the metadata row exists.
- For product/channel apps, product images are core sample data when product list/detail screens are visual, but they are capped. Generate and upload only a representative subset; use local placeholders or null image fields for the rest.
- Maintain `assets/images/asset-manifest.json` or `assets/sample-media/asset-manifest.json` with file, purpose, source/license, and safety notes.

**Media volume limits (HARD):**

- Product/catalog tables: upload images for at most **min(6, record count)** records by default. If records are category-based, choose 1-2 per category until the cap is reached. All remaining records rely on local placeholder thumbnails or empty-state fallback.
- Evidence/attachment tables: upload sample files for at most **30% of parent records**, capped at **5 files total per table**. Metadata rows can still exist without file bytes.
- User/avatar/equipment/site images: upload at most **3 per table** unless the user explicitly asks for a larger visual demo.
- Never create more than **10 generated media files total** in one `/add-sample-data` run without explicit user approval.
- If the calculated sample row count exceeds the media cap, prefer diverse coverage over volume: one hero product, one secondary product, one edge/status example, then placeholders.

**How sample images are inserted:**

1. Generate the normal record body first (name, lookup fields, status, etc.).
2. For **Image columns**, attach a compact base64 payload in the create/PATCH body only if the generated model/service expects base64 for that column. Keep the image small enough for mobile thumbnails.
3. For **File columns**, do NOT put bytes/base64/URLs in the create body. Insert the metadata row first, capture the GUID from Step 5's `BATCH-RECORDS` result, then upload the generated file to `(recordId, columnName)` in Step 5d.
4. Stop once the media cap is reached. Remaining records keep null Image/File columns and use local placeholders in the app UI.
5. If no upload helper exists for File columns, leave the column null and report `sample media skipped — upload helper missing`. Never fake File/Image data with a URL string.

**Pull context from the requirements brief.** The user described what the app does (e.g. "HVAC inspection app for field technicians"); use that to flavor the data — sites named after streets typical for the user's industry, statuses in the right vocabulary. Generic Lorem Ipsum is the failure mode.

**Per-parent fanout floor (HARD).** For every child table in Tier K+, generate AT LEAST 1 row per parent row from Tier K-1 unless the relationship is explicitly optional (`RequiredLevel: None` in the manifest AND the column name doesn't imply 1-to-many like `*audit*`, `*inspection*`, `*order*`). Without this floor, random lookup distribution leaves some parents with zero children and the parent's detail screen renders empty. Concrete rule: if generating `audit_zones` and there are 5 audits, generate AT LEAST 5 zones (one per audit), then add 0-2 more per audit until you hit the per-class count target. Never the reverse — never generate `N` total and let chance decide which parent each row picks.

**State / status distribution (HARD for transactional, issue, override classes).** If a table has a `status` / `state` / `phase` / `severity` / `priority` choice column, **distribute rows across at least 2 distinct values** — never all-`Open`, never all-`InProgress`. Concrete rules:

- **Transactional** (`audit`, `inspection`, `order`): mix at least 3 lifecycle states from the option set if available — typically 1 row in an early state (`Draft` / `InProgress`), 1 in a mid state (`Submitted` / `PendingReview`), and the rest in a terminal state (`Signed` / `Completed` / `Closed`). If only 2 states exist, split ~60/40.
- **Issue / finding**: mix severity AND status independently. If 5 issues across 3 severities × 2 statuses, aim for ≥1 of each severity AND ≥1 `Open` AND ≥1 `Resolved`.
- **Override / approval**: at least 1 row in the queue-driving state (`Pending`) so the queue/inbox tab shows content; remainder distributed across `Approved` / `Rejected`.
- **Log / event**: at least 2 distinct `eventtype` values per parent (e.g. `Created` + `StatusChanged`).

**Date distribution (transactional / log only).** If the table has a `createdon` / `submittedat` / `completedat` / `eventtimestamp` column, spread rows across **today + last 14 days** (not all today, not all 30 days ago). Distribution: ~30% today/yesterday (drives "Recent activity" tiles), ~50% last 7 days, ~20% 8-14 days. Reference and detail tables can use any reasonable date — only transactional/log need temporal spread.

**Scenario archetype edge coverage (HARD when detectable).** Use the selected Power Apps scenario archetype to ensure at least one row exercises each critical state the app UI promises:

- Field inspection / audit: one blocked or evidence-missing audit, one in-progress audit, one completed/signed audit.
- Asset maintenance: one overdue/high-priority work order, one awaiting-parts order, one completed order.
- Inventory scan-first: one variance, one zero/low-stock item, one resolved count/transfer.
- Approvals: one pending item in the approval inbox, one approved, one rejected/changes-requested.
- CRM: one at-risk relationship, one upcoming follow-up, one healthy/won relationship.
- Retail catalog/order: one visual product, one low-stock/backordered example, one order with multiple lines.
- Case management: one escalated/SLA-risk case, one waiting-on-customer case, one resolved case.
- Onboarding/training: one overdue required module, one in-progress module, one completed/certified module.
- Expense/request intake: one missing-receipt draft, one pending approval, one approved/paid request.
- Health/wellness/care: one due-today care task, one missed/follow-up task, one completed/on-track goal.

#### Step 4b — Discover Choice options

For every choice column in the selected tables, query its option set before generating rows:

```bash
node "${CLAUDE_SKILL_DIR}/../../scripts/dataverse-request.js"  GET \
  "EntityDefinitions(LogicalName='')/Attributes(LogicalName='')/Microsoft.Dynamics.CRM.PicklistAttributeMetadata?\$expand=OptionSet"
```

Use the actual `Value` integers from the response. Don't hardcode `100000000`-style values — they vary per environment.

#### Step 4c — Preview to user

For each table, show a markdown table previewing the rows directly in the conversation:

```markdown
### Job Site (cr3e9_jobsite) — 5 records

| Site Name | Address | Square Feet | Active |
|---|---|---|---|
| Westside Construction Site | 4521 Industrial Pkwy | 12500 | true |
| Downtown Office Building   | 188 Main St           |  3200 | true |
| Eastgate Warehouse         | 9047 Logistics Way    | 28000 | false |
| Riverside Retail Plaza     | 320 River Rd          |  6800 | true |
| North Hills Distribution   | 1612 Highland Ave     | 18900 | true |
```

For tables with lookups, also show which parent record each child references:

> `cr3e9_inspection rows reference cr3e9_jobsite records by SiteName above.`

#### Step 4d — Proceed to insert

After

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [LeonardSEO](https://github.com/LeonardSEO)
- **Source:** [LeonardSEO/power-platform-skills-codex](https://github.com/LeonardSEO/power-platform-skills-codex)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-leonardseo-power-platform-skills-codex-add-sample-data
- Seller: https://agentstack.voostack.com/s/leonardseo
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
