---
name: alis
description: "Build the ALIS (Medtelligent) staff bulk-import .xls (13-column template) for Cascades of Tucson (communityId 622) and read the live staff roster via the read-only API. Triggers: alis, alisonline, medtelligent, alis staff import, add staff to alis, alis security roles."
---

# ALIS Skill (Medtelligent) — staff import builder + roster reference

ALIS is Medtelligent's assisted-living EHR. This skill exists to **create/change staff
(and their logins) in bulk**, which in ALIS is done by **uploading an .xls in the web UI**
(`Staff -> Import`) — there is **no staff-write API**. The skill:

1. **Reads** the live staff roster via the ALIS API (read-only) to learn how staff are set
   up — the security-role and job-role vocabulary, and a **job-role → security-role map**.
2. **Builds** a correctly-formatted import workbook from your CSV/JSON of new hires,
   inferring each person's Security Roles from that reference so new staff match existing
   ones. You upload it in ALIS and fine-tune there.

## Running the CLI

```bash
PY="bash $CLAUDETOOLS_ROOT/.claude/scripts/py.sh"
ALIS="$PY C:/claudetools/.claude/skills/alis/scripts/alis.py"

# --- read (reference) ---
$ALIS auth-test                       # mint token, confirm scope
$ALIS communities                     # communityId(s) in scope (Cascades = 622)
$ALIS staff --status Hired --limit 20 # roster
$ALIS roles                           # live security + job role vocabulary
$ALIS role-map [--refresh]            # jobRole -> securityRole(s), learned from Hired staff

# --- build (the deliverable) ---
$ALIS template --out new_staff.xls    # blank, exactly-formatted template to hand-fill
$ALIS build-import --input hires.csv --out import.xls   # NEW staff (create format)
$ALIS build-import --input hires.csv --out import.xls --gen-passwords  # also mint logins
$ALIS build-import --input edits.csv --out upd.xls --format update     # edit existing (ALIS ID)
$ALIS inspect import.xls              # dump a workbook to verify before upload
```

Transport auto-selects httpx if installed, else stdlib urllib. Workbook I/O needs
`xlwt` (write .xls) + `xlrd` (read .xls); `openpyxl` for .xlsx.

## Credentials

Never hardcoded. The user login (used to mint a JWT) loads from the SOPS vault:

```
bash "$CLAUDETOOLS_ROOT/.claude/scripts/vault.sh" \
     get-field clients/cascades-tucson/alis-api-howard-user.sops.yaml credentials.username
```

**Auth model (critical, verified live 2026-06-29):** `POST /user/tokens` with
`{username, password}` returns a JWT (`accessToken`, ~1h) + `refreshToken`; send
`Authorization: Bearer <accessToken>`. The **username must be tenant-qualified** —
`howard.enos@cascadestucson`, not bare `howard.enos` (bare returns HTTP 400). Env overrides
for testing: `ALIS_USERNAME`, `ALIS_PASSWORD`, `ALIS_BASE_URL`, `ALIS_COMMUNITY_ID`.

## The import template (what ALIS expects)

13 columns, exact order — Sheet1 header, then one row per staff. Sheet2 carries the
dropdown lists. The builder reproduces both exactly.

| Column | Notes |
|---|---|
| First Name, Last Name | required |
| Staff Record Number | your HR id / match key |
| Security Roles | inferred from Job Role if blank (see role-map) |
| Staff Status | Applicant / **Hired** / Discharged / Rejected (default Hired) |
| Hire Date | date — format unconfirmed, builder passes your string through |
| Login Enabled | Yes / No (auto: Yes if Email+Password present, else No) |
| Email | required if Login Enabled = Yes |
| Password | credential — see below |
| Date of Birth, Gender | Gender: Female / Male |
| Job Role | free text; drives Security Role inference |
| Cell Phone | |

Input headers are matched flexibly (`Job Title`→Job Role, `Cell`/`Mobile`→Cell Phone,
`DOB`→Date of Birth, `Login`→Login Enabled, etc.).

## Passwords (handle with care)

- By default the builder does **not** invent passwords — supply them, or set
  `Login Enabled = No`.
- `--gen-passwords` mints a strong password for each Login-enabled row missing one and writes
  them to a **plaintext sidecar `*_passwords.csv`** next to the .xls. That file is a credential
  set: distribute to staff, then **delete it or vault it**. Never commit it. The terminal
  output never prints passwords.

## Two import formats — create vs update (confirmed from the real export)

ALIS has two layouts; the builder writes either via `--format`:

- **create** (default, new staff): columns include **Password**, no ALIS ID. Rows **without**
  an ALIS ID are **created** as new staff.
- **update** (existing staff): leads with **ALIS ID** (the staffId), no Password. Rows **with**
  an ALIS ID **update** the matching staff member. (This is the format ALIS exports as
  `ALIS_Staff_Update_Import.xls`.)

So the create-vs-update match key is **ALIS ID** — present = update, absent = create.
**Dates are `MM/DD/YYYY`** (confirmed). Security Roles are **comma-separated** and the builder
infers the *full typical combination* for a job role (e.g. `Med Tech` →
`Caregiver (Cascades), Medication Tech`), learned from current staff in `role-map.json`.

Still smart to **test with ONE row first** before a bulk run. Scope: this credential only sees
Cascades of Tucson (communityId 622).

## Scope reality

The ALIS API has **only GET endpoints for staff** — no create/update/delete. Do not try to
"PUT a staff record"; it does not exist. The .xls upload is the supported write path. (The
API *does* have writes for residents/prospects/billing, out of scope for this skill.)

## Files

- `scripts/alis_client.py` — API client (JWT auth via vault, staff/community reads, role-map).
- `scripts/import_builder.py` — template spec, input parsing, validation, role inference,
  password gen, .xls read/write.
- `scripts/alis.py` — CLI.
- `references/api-reference.md` — endpoints, auth, template, role vocabulary.
- `references/role-map.json` — cached job-role → security-role reference (refresh with
  `role-map --refresh`).
