CIM-VITULLIO+ Systems online

Help

Field-by-field help for the screen you're on. Open this from the Help button in the navigation rail -- it opens straight to the section for whatever page you're looking at. For a full walkthrough of how the pieces fit together, see the User Guide. For config.yaml/install settings, see docs/CONFIGURATION.md; for installing the service itself, see packaging/INSTALL.md in the repo.

Getting Started

Log in with your email address (not a username) and password, then work left to right down the nav rail: catalog the source system's structure under Business Objects, describe how the data arrives under Input Sources, compile a Map, and launch a Run -- dry-run first. Runs load data into your target ERP (QAD is the currently supported target).

There's no self-service signup; an Admin creates every account. If your install uses Microsoft Entra ID SSO instead, the login page shows a single "Sign in with Microsoft" button. Locked out? See Account.

An amber environment badge (e.g. DEV) in the tinted top bar means you're on a non-production instance -- safe to experiment, and nothing you do there touches production data. No badge and a normal top bar means you're on the production site. The badge text comes from that instance's web.instance_label server setting; production deliberately leaves it unset.

After logging in you land on Maps. The CIM-VITULLIO+ logo at the top of the rail opens a small menu with the app's front-door links: Open the app (Maps, the main loading page), Project Overview (a standalone page describing what CIM-Vitullio+ is -- features, modules, and deployment -- opens in a new tab), and Documentation (the Documentation hub -- integration docs and training downloads). The left rail groups the screens you use day to day (Business Objects, Input Sources, Maps, Runs, Comparisons) above an Admin group that only Admin/client_admin accounts see, and the rail footer has your account, the domain switcher, this Help button, the User Guide, and Log out. Extractions and Extract Profiles also live in that first group, but only for Admin/client_admin accounts, and only once Show Extractions and Extract Profiles is turned on for this domain (see Admin: System Config).

The panel bottom-right is Ask Andy -- ask it a plain-English question ("how do I do a dry run?") and it searches this Help content and the User Guide for you, answering with the matching section and a link to it. It also drops a short tip about the screen you're on whenever you land somewhere new. See Ask Andy for everything it can (and can't) do.

Still stuck? Check the service's log (journalctl -u cimvp -f on a systemd install) for the underlying error, and the run/extraction/comparison folder's downloadable artifacts for anything specific to one job.

Ask Andy

The panel behind the orb in the bottom-right corner of every screen. Ask it a plain-English question and it finds the matching Help / User Guide section for you; everything beyond that is opt-in.

Capability How it works
Help search Always on, fully offline -- your question is matched against this Help document and the User Guide, and Andy answers with the best-matching section and a link to it. Nothing leaves the server.
Per-screen tips Landing on a screen, Andy drops a short tip about something non-obvious there. Tips rotate -- revisit a screen and you'll usually get a different one.
Smarter Andy Cloud-LLM answers for questions the offline search can't handle. Three gates: an Admin configured Admin: Andy LLM Config, your account holds the Smarter Andy permission (Admin: Users), and you accepted the consent prompt in the panel -- your question text is sent to the configured LLM only after all three. Each question travels with the best-matching Help/User Guide sections so answers are grounded in what the app actually does (never your data). When any gate is closed (or the cloud call fails), Andy falls back to offline search automatically.
Teach Me Mode Any message containing the phrase "teach me" starts a guided, step-by-step walkthrough -- a bare "teach me" (or "teach me mode") gets an opening chat, while "teach me how to compile a map" dives straight into that goal. Every teaching turn is grounded in the current screen's Help section plus the Help/User Guide sections matching your goal, so instructions stick to real screens and buttons. Available once an Admin turns on Enable Teach Me Mode (Admin: System Config, off by default); it uses the same Andy LLM connection as Smarter Andy. If it's off or the connection isn't configured, Andy says so instead of starting.
Reset The small Reset button under Andy's name clears the chat window and everything Andy was remembering from the conversation -- your name, any Teach Me Mode walkthrough in progress, Tool Mode chat history -- leaving the window empty. Your Smarter Andy consent and the Tool Mode toggle are kept, and per-screen tips still appear as you move to other screens.
Persona By default Andy answers with a warm "Italian uncle" personality. A site Admin chooses which roles get it (Ask Andy Persona, Admin: System Config); an unchecked role gets the same answers, just plain and businesslike.

Andy searches documentation, never your data -- he can't see your maps, runs, or records, and can't click anything for you. For field-level detail on the current screen, the Help button in the rail is the faster path; Andy is best for "where do I..." and "why is..." questions when you don't know which screen to look at.

Logging In

Log in with your email and password -- or, on an SSO install, the single Sign in with Microsoft button. Landing on a "Select a client domain" page right after is normal, not an error.

Field / control What it does
Email Your account's login identifier; not a username.
Password Shown as dots; check Show password to verify what you typed before submitting.
Sign in with Microsoft Shown instead of the password form when the install uses Entra ID SSO -- click it and complete the Microsoft sign-in.
  • "Single sign-on is not configured yet. Contact an admin." means an Admin needs to finish SSO setup at Admin: System Config first.
  • Landing on Select a client domain after login means your account belongs to more than one client domain and the app needs you to pick one -- expected behavior, not an error.
  • A wrong password error on an SSO-only account means the install switched to local mode without a password being set -- ask an Admin to Reset Password for you.

Client Domains

A client domain is a tenant boundary -- maps, runs, extractions, comparisons, connections, and schedules in one domain are invisible from any other. If you can't find work you know exists, check which domain you're in first: the dropdown next to your email (header or rail footer) switches domains at any time without logging out, re-submitting the page you're on under the new domain.

On Admin: Client Domains (site Admin only), each row is one environment of one client:

Column / field What it does
Display Name Rename inline; the slug itself (used in file paths) never changes.
Retention Standard, or Zero retention -- switching to zero offers a Purge existing data now button to wipe run/extraction/comparison history already stored.
Sample Output Folder Where Map Samples bulk generation writes its zips for this domain; leave unset and bulk generation is disabled here.
Members Add/remove by email; you can't remove a non-admin's last remaining domain.
Entities Per-table row counts; Clear Build Order data / Clear Sample Batches appear only when a domain has leftover rows blocking deletion.

Row actions:

Action Effect
Archive Exports the domain to a bundle and removes it from the live database. Reversible -- the archived row keeps a Restore button.
Restore Puts an archived domain back in place, as it was.
Restore into... Puts a copy of an archived domain into another environment of the same client (DEVL/TEST/PROD/OTHER), leaving the archived original alone.
Forget Removes an archived row for good (the bundle file stays on disk).
Delete Permanent, and only allowed once a domain has zero members and zero rows everywhere.

A restored bundle carries that domain's schedules, source connections and webhook endpoints exactly as they were -- still pointed at whatever the original environment talked to -- so they arrive switched off, and a PROD bundle landing in TEST can't start extracting from a production ERP on its old timer or posting to production webhooks. Re-enable them individually once they point somewhere appropriate; tick keep schedules & connections enabled on restore only when you genuinely want them live immediately. (Admin settings -- LLM, ERP connection details, API tokens -- are never in a bundle at all; a restored domain starts with those blank.)

Add a client (with environments) creates a client key plus one or more DEVL/TEST/PROD/OTHER domains at once; Add a single client domain creates one bare domain by slug.

Account

/account (click your email in the rail footer) -- change your own password any time, as long as you know your current one.

Field / control What it does
Current password Must match your existing password -- proof it's really you making the change.
New password The replacement; takes effect immediately on save, and your old password stops working the same moment.
Confirm new password Must match New password exactly -- a mismatch is rejected before anything changes.
Show passwords Reveals all three boxes as plain text while you type; nothing is submitted until you click Change password.

There's no "forgot password" flow for a locked-out account: ask an Admin to click Reset Password for you on Admin: Users, which issues a one-time temporary password. Sign in with it, then come back here to set your own. (Under Entra ID SSO your password is your Microsoft one and this screen doesn't manage it.)

Documentation

The Documentation hub (/documentation) -- open the CIM-VITULLIO+ logo menu at the top of the rail and click Documentation. One page gathering the integration reference and every training download; like all documentation pages it requires being logged in.

Section What's there
Integration docs The Integration Guide, REST API, CLI, Webhooks, and Power Automate references, rendered in-app with an "On this page" outline. Cross-links between them stay inside the app.
OpenAPI spec openapi.json, the machine-readable REST API description -- the same spec the live server exposes at /openapi.json (with interactive Swagger UI at /docs).
JSON Schemas The webhook-event, job-status, and job-submit-response payload schemas, for validating integrations.
Training materials The Complete Training Guide (PDF and Word) plus the Admin, Consultant, and Viewer guides and PowerPoint training decks. Labeled by role, but every download is available to everyone.

The User Guide, this Help, and the Project Overview aren't duplicated here -- they keep their own rail and logo-menu links.

Business Objects

Sorting, filtering & deleting. Click a column header to sort, type in the search box to filter rows as you type, or open a column's filter dropdown to show only the values you pick. Admins and client admins can tick the row checkboxes and press Delete selected to bulk delete several records at once; everyone else (and any single-record delete) uses the Danger Zone at the bottom of a record's detail page.

A Business Object catalogs a legacy system's own multi-table structure once -- by Software name + Software version + Object name (e.g. Epicor / 10.2 / Sales Order) -- and reuses it across every client migrating off that system.

Creating one (Add a business object):

Field What it does
Software name The legacy system, e.g. Epicor, Sage 100, QAD eB2.
Software version e.g. 10.2, 2019, eB2.1.
Object name The business document, e.g. Sales Order.
Description Free text, shown in the list.
Global (Admin-only checkbox) Checked: visible to every domain (shows a green Default badge). Unchecked: visible only to yours (a custom badge).

Already have a custom object that should have been Global all along? An Admin can convert it in place with the Make Global button on its detail page -- it promotes the object and every one of its tables and fields together, and fails if a Global object already exists with the same software name, version, and object name.

Inside an object, per table (Add a table, then Add field rows):

Field property Meaning
Type string, number, date, or boolean.
Required Descriptive flag carried into maps and extract programs.
Extent Array size; 0 = not an array.
Key Part of this table's primary key -- flag it, it's what the join-suggestion tools use to auto-match parent/child fields. Every existing field row carries a key toggle (set key / key ✓), so the flag can be flipped any time -- no need to delete and re-add the field.

The Table relationships panel above the table cards draws every table as one row in a compact tree. Drag a table's name onto another name to make it that table's child -- a dialog collects the join field pair(s), and its Suggest joins button pre-fills likely pairs for that one link. A linked row shows its join fields inline, with Edit joins and Remove parent link buttons.

Auto-build relationships (in the panel header once the object has two or more tables) proposes the whole hierarchy in one pass: a parent and join fields for every table that isn't linked yet, each proposal tagged heuristic (name + Key matching) or AI. Nothing is saved from the review list -- untick anything you don't want, then Apply selected. Already-linked tables are never touched; Remove parent link first to re-propose one. The AI pass needs the Admin: LLM Config AI configured and your account's AI Analysis (schema) permission -- it sends table and field names only, never data; without them the button still works, heuristics only.

Import a structure adds to what's there, it never replaces it. Sources:

Source What you provide
Structure JSON (paste) A JSON structure describing tables and fields.
.xsd file (upload) / .xsd (paste text) A schema file or pasted text -- e.g. a Schema Scan's output. The button becomes Preview tables: a checklist opens first, and nothing is written until you confirm which tables to keep with Import selected tables.
REST API (Business Document) A document from the configured catalog; arrives with header/detail nesting pre-wired (fill joins with the tools above).
Qxtend Schema (QDoc) A QDoc; arrives with relationships and join fields pre-wired from the schema itself.
QAD Adaptive Import Template (upload) Each field's name comes from the template's urn row (the technical identifier, e.g. InventoryStatus); the human column header ("Inventory Status") is kept as the field's description. The template's Row Data marker column is kept as an ordinary field at its template position, so the imported field order mirrors the file layout headerless (Header Row 0) runs parse by -- delete it only if your input files won't carry that column. The offline extract program uses only the names, and writes the Row Data column as a blank placeholder at its position (no database field backs it). A multi-record-type template's own urn structure (which QAD interface each record type belongs to) usually gives it the same pre-wired parent, join fields, and Key flags a QDoc import gets. A single-record-type template, or one whose urns don't resolve cleanly, still arrives flat with no Key flags -- flip the key toggle on the fields that identify each record afterwards.

Auto-wire table relationships (a checkbox on the import form) skips the review step entirely: the same free name/Key heuristics Auto-build relationships uses are applied and saved as soon as the import lands, so a flat source arrives with its hierarchy already built. It fills two different gaps depending on the source -- a parent and join fields for tables that arrive with no parent (.xsd, most structure JSON, or an Adaptive template whose urns didn't resolve cleanly), or just the missing join fields for tables that arrive already nested (REST Business Documents). A Qxtend QDoc needs neither, and neither does a normal multi-record-type Adaptive template -- its own urn structure already supplies parent, join fields, and Key flags (see the import table above), so ticking the box for either only picks up whatever that structure couldn't place on its own.

Two things it deliberately does not do: it never runs the AI pass (only the free heuristics, so the same import always produces the same structure regardless of who runs it), and it only ever touches the tables that import just created -- anything already on the object keeps its own links, even though existing tables are offered as candidate parents for the new ones. A banner afterwards reports what happened: relationships read straight from the imported structure (QDoc, REST nesting, or a normal Adaptive template), how many more tables the free heuristics linked when the checkbox was ticked, or that nothing matched confidently. Because a heuristic link was never reviewed first, check the relationships tree and fix any wrong pair with Edit joins; leave the box unticked when you'd rather approve each heuristic link yourself -- relationships the import itself declared land either way.

Delete business object (bottom of the detail page) removes the object and all its tables/fields; per-table and per-field Delete buttons remove smaller pieces. None of these can be undone.

Input Sources

Sorting, filtering & deleting. Click a column header to sort, type in the search box to filter rows as you type, or open a column's filter dropdown to show only the values you pick. Admins and client admins can tick the row checkboxes and press Delete selected to bulk delete several records at once; everyone else (and any single-record delete) uses the Danger Zone at the bottom of a record's detail page.

An Input Source records how one client's data actually arrives, separately from the Business Object structure it matches. Either pick a Kind and describe the arrival format, or skip all of that and Link to a Business Object instead -- a linked source's fields always resolve live from that structure, and none of the per-kind fields below are used.

Kind What you provide
Excel / CSV file (upload a sample) A sample .xlsx/.xlsm/.csv; its header row becomes the field list.
Custom-delimited file (upload a sample) A sample .csv/.txt/.tsv plus the Delimiter (exactly one character).
QAD Adaptive Import/Export template (upload) A blank "Export With Import Format" template or an app-exported data file. Field names come from the urn row's technical Table.Field names (Site.SiteCode) -- these stay unique even when several columns share one header label (an Adaptive Sites template labels both Site.DomainCode and Site.OwnershipDomainCode "Domain"). The human column header is kept as the field's description.
.xsd file (upload) A schema file, plus an optional Database filter (blank imports every database in the file).
REST API (Business Document) A Business Document from the configured catalog. "No REST catalog configured" means Settings.rest_specs_dir isn't set for this install.
Qxtend Schema (QDoc) A QDoc from the configured schema library (Settings.schema_dir).
MCP server (manual field list) A registered server's name plus the fields typed one per line, as name or name:data_type (string/number/date/boolean).

Below the per-kind fields, the optional Starting-map defaults section (ad-hoc sources only) prefills the Source tab of every starting map built from this source -- each map can still change them afterwards:

Field Meaning
Header row Which row of the arriving file holds the column headers (blank means 1; 0 means no header row, data read by position). For a sample-file source it also says which row of the uploaded sample the field list is read from, so 0 isn't allowed there.
File format csv or xlsx; leave on automatic to derive it from the kind (a delimited source is csv, an Adaptive template xlsx, an Excel/CSV sample follows its extension).
Delimiter csv format only: the single character separating columns (blank means comma). Hidden for the Custom-delimited kind, whose own Delimiter field already carries through to maps.
Key column(s) The column(s) uniquely identifying each record, comma-separated for a composite key (pt_part,pt_site). Each must be one of the source's resolved fields.

A source with a saved legacy-schema extract mapping (see Extract program... below) prefills from that mapping instead of the kind: its generated program's output file is what runs will consume, so starting maps arrive set to csv, the mapping's delimiter, and Header Row 0 (no header row) -- this works for a Business-Object-linked source too. Explicit Starting-map defaults still win over both layers.

Once created, an Input Source is selectable from Create a Starting Map or a map's Source tab. An unlinked source's detail page has a Refresh fields button to re-resolve its field list later. (An eighth kind, SSH extract, isn't created here -- it's created from a Schema Scan's detail page.)

Every source's detail page has an Extract program... button opening an overlay that generates a standalone offline extract script (for clients with no SSH access; run it by hand on the source system and upload its output as run input). Two paths:

  • Download as-is -- the program uses this source's table/field names directly. Only possible when those names are real database identifiers (a Business-Object-linked source, or an ad-hoc catalog of identifier names); the overlay says why when it isn't.
  • Map to legacy QAD schema -- for a source whose fields are display labels (an Adaptive template, a spreadsheet): upload a schema.xsd of the actual legacy QAD database, select the tables holding the data (a long schema shows a Filter tables by name box above the list -- type part of a table's name or description to narrow it instead of scrolling; ticked tables stay selected while filtered out of view), and map every input source field onto a table.field, a fixed constant (exported verbatim, e.g. yes), or blank (no legacy equivalent -- the column is exported empty). The mapping step works like the Map Editor's Field Map tab, with the same Visual / Grid toggle: in the Visual view (the default), drag a schema field onto an input source field to connect the two with a line (either direction works) -- both columns re-sort so the line runs straight across, the same as the Field Map tab -- or drop an input source field on the Constant or Blank box across the top -- Clear / unmap removes a mapping. Each pane has its own filter box and a Sort by: Name / Label toggle (plus an Unmapped only tick on the right), and a Freeze order box holds the columns still when the re-shuffling is more distracting than helpful (re-sorting is display only; the exported file's column order is unchanged). No mouse needed: Enter/Space picks a focused chip up, Enter on the other side completes the pair, Escape cancels, and Ctrl/Cmd+Z undoes. The Grid view keeps the dropdown-per-field table; both views edit the same mapping, so switch any time. Auto-wire joins proposes how a child table's rows find their parent row, the same way the Business Object import does; Auto-map proposes field mappings by meaning, weighing table names, field names, and field labels on both sides (with AI when your account has the AI analysis permission and Admin > LLM Configuration is set up, name/label matching otherwise) -- suggestions only pre-fill the mapping for review, nothing is saved until you save. Blank unmapped assigns every field still left unmapped to blank at once, so the mapping is complete enough to generate without clicking each leftover field -- a single Undo reverses it. The generated program writes one delimited file (delimiter is chosen in the overlay, | by default), no header row, all columns in input source order; each row type fills only its own table's columns, parent rows followed by their child rows. The uploaded schema and the mapping are saved with the source, so you can reopen, tweak, and regenerate. For a Business-Object-linked source, Apply key flags to Business Object back-fills the linked object's Key flags from this mapping: every field mapped to a legacy column in its table's primary index gets flagged, after a confirmation listing exactly which fields -- existing flags are never cleared. That's the way to get real key information onto a structure whose own import carried none (an Adaptive template's urn names never match the legacy identifiers, so only the mapping can bridge the two).

Once at least one table is selected, an Extra WHERE conditions (optional) section shows a 3-line, vertically-scrolling text box per selected table. Whatever you type is added directly to that table's generated WHERE clause, so include your own leading and/where -- typing and loc_type <> "MRO" for loc_mstr turns where loc_mstr.loc_domain = global_domain: into where loc_mstr.loc_domain = global_domain followed by and loc_type <> "MRO":. It's checked for balanced quotes/parentheses and for constructs that could break out of the extract's read-only query (a stray ., :, { }, or /* */) -- not a full ABL syntax check -- and rejected with the offending table named if it fails. A mapping with a rejected WHERE box still saves (so the work isn't lost) but shows as not complete until you fix it.

Every generated extract program starts with {mfdeclre.i} and restricts any domain-named field (domain / *_domain) to the QAD session's global_domain, so it must be run from a QAD environment session and always extracts the current domain only.

The Input Sources list page also has a Download extraction launcher (.p) link -- not tied to any one source or Business Object. It downloads extract_menu.p, a standalone Progress ChUI menu that lists and runs whichever extraction .p programs are installed in a directory on the source (Progress/QAD) host; drop it into that directory and run _progres -pf <bootstrap.pf> -p extract_menu.p.

Maps

Sorting, filtering & deleting. Click a column header to sort, type in the search box to filter rows as you type, or open a column's filter dropdown to show only the values you pick. Admins and client admins can tick the row checkboxes and press Delete selected to bulk delete whole maps (all their versions) at once. To delete a single version -- or one whole map -- open the map version and use the Danger Zone at the bottom, which offers both Delete this version and Delete entire map. A map can't be deleted while runs still reference it; delete those runs first.

Each row is one map name; Versions links every compiled version. Compiling the same name again always creates the next version -- nothing is ever overwritten.

On a version's detail page:

Control / panel What it does
Download workbook Regenerates the .xlsx exactly as compiled. On the Field Map tab, Target Field displays table-qualified as rowtype.fieldname (same shape as the web view) and is Excel-protected against editing; Source Column carries an Excel dropdown validated against the Source tab's own declared columns.
Edit this version Opens the Map Editor.
Delete this version Blocked while any run still references it -- delete those runs first.
Owner & Reviewers Feed into webhook notification payloads (see Admin: Notifications) so a receiving flow can @mention them; editable by Admin/client_admin/Consultant once the domain has members to pick from.
Target / Source / Field Map / Row Types A read-only view of exactly what this version compiled to. The Field Map table shows Target Field table-qualified as rowtype.fieldname, matching the Map Editor's grid.

Compile a Map

/maps/compile (Upload / compile a workbook on Maps) -- validates a map workbook and registers it as the next version of its map name.

Field What it does
Map name Reusing an existing name creates that map's next version; a new name starts at v1. Nothing is ever overwritten either way.
Workbook (.xlsx) The map workbook -- Source, Target, and Field Map tabs required; Row Types, Rules, and xref tabs optional.
Adaptive Import Template Only needed when the workbook's Target Adapter Type is adaptive_excel -- the blank QAD template the load will be written into.

A failed compile reports every problem found in one pass -- tab, row, and column -- not just the first, so fix the whole list and re-upload once. A failed compile never creates or damages a version. Success links straight to the new version's detail page. Prefer not to author a workbook at all? Create a Starting Map generates one instead.

Create a Starting Map

Generates a ready-to-edit map (registered immediately as v1) plus a matching sample-data file, downloaded together as a zip -- instead of authoring a workbook from a blank sheet. On the generated workbook's Field Map tab, Target Field displays table-qualified (rowtype.fieldname) and is Excel-protected -- it comes from the target schema, never hand-typed -- and Source Column carries an Excel dropdown validated against the Source tab's own declared columns. File Format, Data Type, Required, Adapter Type, Operation, Failure Policy, Rule Type, Miss Policy, and Row Type are all Excel dropdowns too; Source Column and Row Type stay in sync with rows you add to the Source/Row Types tabs after downloading (no re-download needed), but a fixed-list dropdown (Adapter Type, Rule Type, Miss Policy, etc.) is a closed set the app itself enforces, not something to extend by hand. Field Type only appears as a column/dropdown when this domain has AI address analysis turned on (Admin: System Config); the four Extraction * columns only appear when Extractions is turned on -- both off by default. Re-download the map (its detail page's Download workbook link) after either toggle changes to see the updated columns.

Field What it does
Output format REST API (Business Document), Qxtend Schema (QDoc), or an uploaded QAD Adaptive Import Template -- the format the generated map will load into, which also picks which catalog the document dropdown below draws from. Both dropdowns are searchable -- click and type any part of the name. "not configured" means that catalog isn't set up for this install (Settings.rest_specs_dir / Settings.schema_dir).
Input source (optional) Leave as "mirror target fields" to map the target schema onto itself 1:1, or pick an Input Source to build the Source tab from that source's real fields instead -- name-matches prefill as direct rules and everything else arrives as blank for you to complete.
Choose which tables and fields to include Inserts a Choose tables & fields step before the map is built. No table or field in a business document, QDoc, Adaptive template, Business Object or Input Source is required for a map -- untick what this map doesn't need and it simply isn't generated. Leave the box off to include everything (the original behavior).
Use AI to suggest field mappings, and review before creating Only shown once an Input Source is picked (nothing left for AI to add when mirroring) and only if your account has the AI Analysis (schema) permission -- field names only, never data. Runs the configured LLM over whatever the name-matching couldn't resolve, then opens the Map Editor with its suggestions -- nothing is written until you click Create map there. Runs after the field selection, so AI only sees the fields you kept.
Map name Auto-fills from the chosen document/QDoc/filename; type your own to override.

On the Choose tables & fields step:

Control What it does
Output -- target fields One collapsible group per target table (row type), every field ticked to start. An unticked field gets no Field Map rule; a table with nothing left ticked also drops off the Row Types tab entirely.
Input -- source fields Only appears when an Input Source is selected, grouped by its Business Object table. An unticked field gets no Source tab column and never shows up in the Field Map's Source Column dropdown.
Table checkbox The box on a group header ticks or unticks that whole table at once; it shows a dash when only some of the table's fields are ticked.
Select all / Clear all Applies to that panel only -- the output and input lists are independent.
Filter fields Hides non-matching rows as you type. Hiding never changes a tick, so filtering can't silently drop a field.
Create map Builds and compiles the map from just the ticked fields. You must keep at least one output field, and at least one input field when an Input Source is in play.

Nothing is written until you click Create map -- backing out to Create a starting map discards the selection. Changed your mind later? The Map Editor can add a Source tab column back by hand, or you can regenerate the map with a wider selection (it lands as a new version).

Map Editor

/maps/{name}/v{version}/edit -- five tabs, all edited directly in the browser. Save & Recompile re-validates everything like a workbook compile and always creates a new version -- it never touches the original .xlsx or an existing version.

Tab What's on it
Target Adapter Type, Business Document/QDoc Name, Operation, Batch Size, Failure Policy (+ Failure Threshold % when policy is threshold), QDoc Version. Operation is create (insert new records only), update (change existing records only), or create_or_update (rest adapter only: insert, and any record QAD reports as already existing is automatically resent as an update -- use it for files that mix new and existing records; newly generated rest starter maps default to it).
Source Header Row, File Format, Delimiter (csv only; the single column-separator character, blank meaning comma), Key Column(s), and the column table (Name / Data Type / Required / Label). Key Column(s) is a dropdown with a checkbox per declared column, naming the source column(s) that uniquely identify each record -- tick two or more for a composite key when no single column is unique (e.g. pt_part + pt_site); the current picks are listed under the closed dropdown. The key never restricts what loads: it labels records in run reports and error listings, and it is the default Key column(s) for Launch a Comparison and Verify Loaded Data. In the workbook .xlsx the Source sheet's Key Column cell holds the same thing comma-separated (pt_part,pt_site). + Add column appends a row; the ↑/↓ buttons reorder rows -- column order is what a Header Row 0 map reads the file by, so a positionally misplaced column (e.g. a missing Row Data placeholder) is fixed by moving it, not retyping the list. Header Row is the 1-based row of the input file that holds the column headers; set it to 0 for a file with no header row at all -- every row is then treated as data, and each Source column reads the file column at its own position, in the order the columns are listed on this tab.
Field Map One row per target field, in either of two views -- the visual mapper or the grid. See below.
Row Types Name, Sort order, and an optional Cond Field that must be non-blank for that row type's record to be emitted at all.
Xrefs One editable table per xref tab; paste a CSV/TSV block to replace an entire table's rows in one go, or upload a CSV/TSV/XLSX file to create a new tab or add/update rows in an existing one -- see below.

Source-tab Required is descriptive only -- a run is never rejected for a blank value in a Required column; whether a field must actually be populated is enforced by the target (REST API, QXtend QDoc, or Adaptive Excel import) when the data is sent, not by Parse.

A few QAD REST fields are declared boolean in QAD's own spec but really hold a label pair (e.g. siteType: "Primary or Connection") -- newly generated maps declare such columns string and the value is sent to QAD verbatim. If an older starter workbook declared one boolean and Parse rejects values like 'Primary', flip its Source-tab Data Type to string here.

Header + detail on a REST target. When a REST-target map has Row Types (the shape a starter map gets for a Business Document with child tables -- a root row type named after the Business Document plus one per child array), each source row is sent to the target ERP as one nested Business Document record: the child rows are placed inside the header object under the array properties QAD's spec declares, mirroring how a QXtend map sends one QDoc per record group. A record group is only ever sent whole -- if one of its rows is rejected before sending, the whole group is held back, with the culprit row's message naming the bad field. Batch Size counts nested documents here, not individual rows. Structure comes from the vendored QAD OpenAPI spec corpus (see rest_specs_dir in the service config); if no spec matches the Business Document, rows fall back to being sent flat, exactly as they were before nesting existed.

Post-write readback validation. Immediately after each REST-target batch call succeeds, every record it just wrote is re-fetched by the Business Document's own key (the same fields QAD's DELETE operation requires -- from that same vendored spec corpus) and checked that the key field values actually landed. This catches a write QAD's own save response said succeeded but never actually persisted -- rare, but the same class of problem Verify loaded data exists to catch, just far cheaper: one small GET per record instead of a full readback extraction and comparison. It runs automatically whenever the key can be resolved -- silently skipped when it can't (no catalog configured for this Business Document, or a record missing one of its own key field values, e.g. a key QAD assigns itself on create). A record that fails it shows up as an ordinary Load results error, e.g. "QAD accepted this record as saved, but a readback immediately after could not find it" -- same place, same downloads/reports, as any other load failure. The readback GETs for each batch run several at a time (writes never do -- batches still go up strictly one at a time), and on a big trusted load the check can be skipped for one run with Skip per-record readback check on Launch a Run.

Batch Size tuning. Batch Size (Target tab, default 50) is how many records (or nested documents) each REST/QXtend call carries -- fewer, larger calls means fewer round-trips, so raising it (100-200) noticeably speeds up big clean loads. The trade-off: when any record in a batch is rejected, the whole batch call fails and every record in it is resent individually so the one bad row can't hide its neighbors -- so on a file with many bad rows, a large Batch Size pays for itself in isolation resends. Rule of thumb: raise it for bulk loads of data that already dry-runs clean; keep the default while a file is still being cleaned up.

Field Map has two views, switched with the Visual / Grid toggle at the top of the tab (remembered per map). Both edit exactly the same rules -- switch back and forth mid-edit and nothing is lost. Nothing is saved in either view until Save & Recompile.

Visual view (the default). Source fields on the left, target fields on the right, and a line drawn for every mapping between them:

Control What it does
Drag a source field onto a target field Creates a direct mapping -- the input value copied straight through. No dialog.
Drag a target field onto a rule box (Formula / Xref lookup / Constant / Blank) Sets that rule type and opens a dialog for its details. Dropping on Unmap clears the mapping instead. The Constant / Blank dialog has a Map as blank button to switch to blank without redragging.
Drag a source field onto a rule box Same, and pre-fills the source column -- drop on Formula and the builder opens with that column already inserted.
Click a connector line, or a target field's edit Opens that rule's details, including Row Type, Field Type and Target Label.
A target field's × Unmaps it (back to rule type blank).
Sort by: Name / Label Orders each column by field name or by field label, independently per side.
Filter boxes / Unmapped only Narrow either column; Unmapped only hides target fields that already have a rule.
Row-type group headers Click to collapse or expand; each shows how many of its fields are mapped. Dropping into a group sets that rule's Row Type.
Undo (or Ctrl/Cmd+Z) Steps back through mapping changes made in this view.
Blank unmapped Assigns every still-unmapped target field to blank in one click -- including target columns the workbook left out, which then export as empty columns. Enabled only when something is still unmapped; a single Undo reverses it.
Freeze order Stops both columns re-sorting themselves after each change.

Both columns re-sort themselves after every mapping so the connector lines run straight across instead of tangling. That works out to no crossings at all as long as each mapping is one source to one target; a formula reading several columns, or one source column feeding several target fields, has to cross something no matter how the columns are ordered -- those lines are drawn dashed. Tick Freeze order if you would rather fields stayed put.

Both columns share one scrolling window and move together -- there are no separate scrollbars to keep in sync, and each column's filter and sort controls stay pinned at the top while you scroll. Dragging scrolls it for you: hold the pointer near the top or bottom edge of the columns and they scroll while you drag, so the two fields never both have to be on screen at once. Near the top or bottom of the browser window the page itself scrolls, which is how you reach the rule boxes from far down a long list.

You don't have to drag at all. Tab to any field and press Enter or Space to pick it up, then Enter on a field in the other column (or on a rule box) to complete the mapping; Escape cancels. The line above the columns says what is picked up and what to do next.

The strip above the columns counts how many target fields are mapped and warns when required target fields are still unmapped. A direct mapping whose data types disagree (text into a numeric field, say) gets a warning badge -- it never blocks saving. A rule pointing at a source column that no longer exists is drawn as a dashed red line.

Grid view is the spreadsheet-style table: one row per rule, one column per rule setting. It is the place to do bulk work -- click a column header to sort rows by it (visual and saved row order only -- no rule behavior changes), drag headers to reorder, drag their edges to resize, and use the Columns button to hide/re-show columns, all saved per map for everyone.

The four Extraction Family / Dataset / Key / Value columns only appear when this client domain has Extractions turned on (Admin: System Config, off by default). They're for round-trip xref -- looking a value up in extracted data instead of a workbook Xref tab. With Extractions off they're hidden from the grid and from the Xref dialog; any map that already uses them keeps working and keeps its values on save.

Field Map Rule Type drives which other columns on that row apply:

Rule type Uses
direct Source Column -- copy the input value through.
formula Formula text -- opens the formula builder (below).
xref Xref Tab + key/value columns, or the four Extraction Profile fields for round-trip mode; Miss Policy says what happens on a failed lookup.
constant Default Value -- the same value on every record.
blank Nothing -- emits an empty value (and marks "still to be mapped" work).

Multi-column (composite) xref lookups. An xref rule's Source Column and Xref Key Column each accept a comma-separated list for a lookup keyed on more than one column at once (e.g. Source Column Part,Site matched against Xref Key Column PartKey,SiteKey) -- same convention as the Source tab's own composite Key Column(s). One lookup still only produces one value column; a target that needs several xref-derived fields off the same composite key uses one Field Map row per field, all naming the same Xref Tab and Xref Key Column(s) but their own Xref Value Column. Miss Policy pass_through isn't supported on a composite key (there's no single column left to copy through) -- pick a different Miss Policy for those rules.

Uploading a file to build an Xref tab. Each Xref tab has an Upload box below its table and paste box, and the Xrefs tab as a whole has one next to + Add xref tab for starting a brand-new tab from a file. Pick a CSV/TSV/XLSX file, leave Column headers in file ticked when row 1 holds column names (untick it for a headerless file -- columns come back named Column1, Column2, ...), and click Upload. Uploading into an existing tab adds the new rows to what's already there (columns are unioned, so a row missing a column just gets blank for it); uploading to build a new tab replaces nothing because there's nothing there yet. Nothing is saved to the map until Save & Recompile -- an upload only changes what's in the browser, same as every other Xrefs-tab edit. A file over ~20,000 data rows is truncated, with a message saying how many rows actually loaded -- split a bigger lookup table and upload it in parts.

Target Field displays table-qualified as rowtype.fieldname -- the same table.field shape Source Column uses -- so two rules producing same-named fields under different row types are tellable apart, and it's read-only: target fields come from the target schema, so an existing rule's target can't be retyped. Only a rule you add with + Add rule has a typeable Target Field (enter the bare field name there; the row's Row Type supplies the table qualifier) -- to change a wrong target, remove the rule and re-add it.

Field labels. Both views show a readable caption above each field's real name -- itemStatusCode reads as "Item Status Code". That caption is worked out from the name unless someone supplied a better one: for a source field, the Source tab's Label column; for a target field, the Field Map's Target Label (also editable in the visual view's rule dialog). The real field name is always shown underneath, so a mapping can be checked against the actual file and schema. Labels are display-only -- nothing in a run matches on them -- and Sort by: Label orders each column by them.

Field Type: address flags a field for AI address analysis on every run (see Runs).

Formula builder. The Formula column opens an overlay instead of a plain text box: click or drag a source column in -- the This row / Whole column toggle inserts {Column Name} (this row's value) or [Column Name] / [Column A:Column B] (whole column or range, for functions like VLOOKUP, INDEX/MATCH, SUMIF(S)) -- click a function from the Functions list to insert it, and type sample values into Test values to see a live computed result (or the exact error) before saving.

This editor doesn't have a Rules tab, but saving does not lose a map's data-quality rules: the Rules tab from the version you started editing is carried forward onto the new version unchanged. To change those rules, re-upload a workbook with the Rules tab you want, from Maps.

Map Samples

Generates a starter map + sample-data workbook for every document in a catalog at once (REST API, Qxtend Schema, or a whole folder of uploaded Adaptive templates), instead of one at a time via Create a Starting Map.

Needs a Sample Output Folder configured for your domain first (see Client Domains) -- the Generate button stays disabled until one is set.

Field / column What it does
Output format Which catalog to sweep: REST API (every Business Document), Qxtend Schema (every QDoc), or Adaptive templates (every template in an uploaded folder). A greyed-out option means that catalog isn't configured for this install.
Adaptive Import Templates folder Adaptive format only -- the folder of blank templates to generate from.
Status queuedrunningsucceeded / failed, updating live on the page. A service restart mid-batch force-marks the batch failed on startup -- re-generate rather than waiting on it.
Download A zip link appears on the row once the batch succeeds -- the whole batch in one archive, also written to the domain's Sample Output Folder.

Each batch's detail page lists any per-document failures (document name plus its error message), so a partial batch isn't a mystery -- each failure names its document, and the rest of the batch still generates.

Extract Profiles

Extraction profiles compile the same way maps do: a source bundle is validated into an immutable version, and compiling the same family again creates the next version.

Admin/client_admin only, and only once Show Extractions and Extract Profiles is turned on for this domain (see Admin: System Config).

Upload / compile a profile bundle:

Field What it does
Family (optional) The profile family name; left blank, it's read from the bundle's own profile.yaml family: key.
Profile bundle (.zip) A zip containing profile.yaml plus an optional scripts/ folder.

Compile overlay (from a family's row) layers one client's differences on the shared base instead of forking it:

Field What it does
Client The client this overlay belongs to.
Base family (optional) Defaults to the family you're compiling under; set it when the overlay's base profile lives under a different family name.
Overlay bundle (.zip) A zip containing overlay.yaml (plus an optional shadow scripts/ folder). Its base_family must match.

Launch an Extraction

Admin/client_admin only, same visibility rule as Extract Profiles.

Field What it does
Profile family / Version Which compiled profile to run.
Client (optional) Selects that client's overlay on top of the base profile; leave blank to run the base as-is.
Overlay version (optional) Defaults to the client's latest.
Connection Which source connection to run against; only connections with a pinned host-key fingerprint show up here -- run Test Connection on a new one first.
Probe only Runs just the profile's fingerprint checks (confirms the host matches what this profile version expects) without pushing, executing, or fetching anything. Good for vetting a new connection or a suspected version drift before committing to a full run.

"No extraction profiles compiled yet" / "No enabled source connections configured" mean exactly that -- compile a profile or add a connection first; the links on the page take you there.

Extractions

Each row's Status badge tracks probe/push/execute/fetch/normalize/ deliver as they happen. A failed probe stops the extraction on purpose -- nothing runs against a host that doesn't look the way the profile expects.

On a completed extraction's detail page:

Section What it shows
Probes Pass/fail per fingerprint check, with exit code and matched output. A failure means the source host doesn't match this profile version's expectations.
Execute Each script step's exit code and ok/failed result.
Fetch How many files were fetched.
Normalize issues Per-dataset issues found while normalizing raw output.
Delivery Either the inbox paths data was dropped into, or the runs it auto-launched (linked under Delivered runs).
Cleanup Whether remote temp files were removed (succeeded / failed / skipped_by_profile / skipped_keep_on_failure / not_created).
  • Draft overlay with AI appears only when a probe failed and your account has the AI on client data permission -- probe diagnostics are raw output from the client's source host, so this asks the domain's Client Data LLM (never the schema-only connection) to draft an overlay.yaml for the deviation it sees. The draft is saved for review, never auto-compiled or run.
  • Purge data (Admin/client_admin, once the extraction is finished) also purges any runs it delivered and comparisons referencing it -- this can't be undone.

Launch a Comparison

Each side (Left/Right) is a Run (pick a finished run of any map -- this is what most comparisons use: "what changed since the last load?"), an Extraction dataset (pick the extraction, then type its Dataset name, e.g. item_master -- only shown when your domain's Extractions feature is enabled, Admin -> System Config), or Upload a file. A Run source diffs that run's own input data as it was loaded, so comparing two runs of the same map needs nothing extra to be configured. Then, under Options:

Option What it does
Key column(s) Comma-separated for a composite key (e.g. pt_part,pt_site); leave blank when both sides deliver to the same compiled map, so its own Key Column(s) -- composite included -- become the default. When a side is a Run or Extraction dataset, a pick-list of that source's actual columns appears under the field (the columns present in both sides, when both are known) -- tick boxes instead of typing. Uploaded files keep free-text entry, since their header isn't known until the comparison runs.
Ignore columns Comma-separated columns excluded from change detection, e.g. last_seen,updated_at.
Case-insensitive value matching / Trim whitespace before comparing Lenient matching for values that differ only in formatting.
On duplicate key Fail the comparison (default), Warn and keep the last occurrence, or Group as child records for hierarchical parent/child files -- a row repeating the key of the record right above it is compared as that record's next child instead of failing as a duplicate.
On column-set mismatch Fail the comparison (default), or Compare the shared columns only.

Child records are handled in every mode: a data row whose key column(s) are all blank -- the QAD CIM-load layout, where a parent row is immediately followed by child rows that fill only their own columns (e.g. Inventory Status transaction-type rows) -- is compared as a child of the nearest keyed row above it, never as a duplicate blank key. Children match child-for-child by position under their parent, and show up in the report keyed as the parent's key plus child 001, child 002, ... in file order. Pick Group as child records only when child rows repeat the parent's key instead of leaving it blank.

Comparisons

Sorting, filtering & deleting. Click a column header to sort, type in the search box to filter rows as you type, or open a column's filter dropdown to show only the values you pick. Admins and client admins can tick the row checkboxes and press Delete selected to bulk delete several comparisons at once; everyone else (and any single-record delete) uses the Danger Zone at the bottom of a comparison's detail page.

Each row is one submitted comparison; click through for the full report. The detail page is organized into sections:

Section What it shows
Summary Record counts per side, matched records, and the four verdict counts -- each with its share (Added as % of the Right side, Removed as % of the Left side, Changed/Unchanged as % of matched records). All counts are parent records: in a hierarchical file a parent and its child rows count as one record.
Sources & settings Both sides' labels, the key column(s), when the report was generated, the options exactly as launched (ignored columns, lenient matching, duplicate-key and column-mismatch policies), and the full compared-column list.
Changes by column Every compared column ranked by how many matched rows it changed in, with its % of matched rows and an example old -> new value -- a zero row is proof that column never moved. Counted per row, so child rows count individually here.
Added rows / Removed rows The first 50 added/removed rows in full (every column, not just the key).
Changed records The first 200 changed records, one row per changed cell (key, column, old, new).
Warnings Anything the diff soft-tolerated (duplicate keys in warn mode, a column-set mismatch in intersection mode, child rows grouped under their parent records).

The Summary verdict counts are parent records -- a parent and its child rows collapse into one record (so a hierarchical file's numbers match the count of real records, not the raw row count):

Count Meaning
Added Parent records present on the Right side only (all of a new parent's child rows included).
Removed Parent records present on the Left side only.
Changed Parent records on both sides where the parent row or any of its child rows differs, or a child row was added/removed under it (after the launch form's ignore/lenient-matching options are applied).
Unchanged Parent records on both sides where the parent and every child row are equal.

One Excel report download (report.xlsx, built fresh each time you click it) carries the complete, uncapped detail in a single styled workbook -- it replaced the old differences/added_rows/removed_rows CSV downloads:

Sheet Contents
Summary Sources, key column(s), the options exactly as launched, and the verdict counts with their shares.
Changed Records One row per changed record showing the full record -- an old/new column pair wherever a column changed somewhere in the comparison, a single column where nothing did -- with each record's actually-changed cells highlighted amber.
Added Rows (green) / Removed Rows (red) Every added/removed row in full, uncapped -- Added Rows is ready to re-use as a load file.
Warnings Anything the diff soft-tolerated.

Detail sheets are never capped short of Excel's own ~1,048,000-row sheet limit; if a sheet does hit it, both that sheet and the Summary say exactly how many rows were cut.

The AI export bundle download (ai-bundle.zip, next to the Excel report) carries the same complete data serialized for machines instead of people: a zip of plain tidy CSVs (changes_by_column, changed_cells, full changed_records_old/changed_records_new, added_rows, removed_rows -- one header row, no styling, no row caps at all) plus a README.md that describes every file and column (the data dictionary) and a compact summary.json. Drop the CSVs and the README into an AI assistant -- or pandas, or Power Query -- and it can analyze the comparison directly, with no workbook formatting in the way. Pick the Excel report to read the results; pick the AI bundle to compute on them.

A comparison that hits a genuine data problem -- a missing file, a duplicate key set to fail, a column-set mismatch set to fail -- fails loudly with a specific error rather than producing a partial or silently-wrong diff; adjust the matching options on the launch form and resubmit. Purge data (Admin/client_admin/Consultant, once finished) removes its stored data but keeps the comparison listed for history; Delete comparison (same roles) additionally removes the record itself, so it disappears from the list entirely. Neither can be undone -- Viewers can only view.

Launch a Run

Pick a map version, upload the input file, and decide whether to dry-run -- always dry-run first for an input file you haven't run before.

Field / control What it does
Map Each dropdown entry is one compiled map version (map name (vN)), newest first -- versions are distinct compiled artifacts with their own rules and target config.
Input file Drag-and-drop or browse; must match that version's Source tab contract. A map whose Source tab sets Header Row to 0 takes a file with no header row at all -- every row is data, read by position in the Source tab's column order. For maps built from a QAD Adaptive input source, both blank "Export With Import Format" templates and app-exported data files work: the file's urn reference row is detected automatically (on the Data sheet when there is one), whatever the Source tab's Header Row says.
Dry run Parses, transforms, and validates everything but never calls ERP -- safe against production, and no ERP credentials are needed (the prompt below is skipped). Produces a preview report and workbook instead of a load.
Skip per-record readback check REST-target loads normally re-fetch every loaded record from QAD right after loading it, confirming the write really persisted (see the Map Editor's post-write readback validation). Ticking this skips that check for this one run -- a big load finishes much faster, but the run then trusts QAD's own save responses. Best for large loads whose data has already dry-run clean; leave unticked when in doubt. Ignored by dry runs and non-REST targets. The same option exists on Launch real run, Re-run failed records, cimvp run --skip-readback, and POST /api/runs (skip_readback=true).

For a real (non-dry) run of a map that loads directly into the target ERP (REST/QXtend targets), clicking Launch pops an ERP credentials prompt (username/password) -- used only for this one run and never stored; you'll be asked again next time. Dry runs skip the prompt -- a dry run never contacts the ERP -- and Excel-target maps skip it entirely, since there's no live ERP call. Runs triggered by CLI, REST API, watch-folder, or a schedule instead use the fallback credentials under Admin: System Config.

Every run is bounded by the server's run timeout (job_timeout_seconds in the service config, default 1 hour): a run that exceeds it is stopped and marked failed, so a hung ERP connection can't block the queue forever.

Runs

Sorting, filtering & deleting the list. Every list (Runs included) is sortable and filterable in place: click a column header to sort by it, type in the search box to keep only matching rows, or open a column's filter dropdown to show only the values you pick. To delete, admins and client admins tick the checkboxes down the left edge and press Delete selected -- a bulk delete of several records in one step; everyone else (and anyone removing a single record) opens the record and uses the Danger Zone at the bottom of its detail page.

A run's final status reflects how many of its records loaded, not just whether any failed. Every trigger (web, CLI/REST API, watch-folder, schedule) funnels into one sequential queue, so a run sitting in queued is just waiting its turn.

Status Meaning
queued Accepted, waiting for the single worker (runs execute one at a time, in order).
running In progress -- the badge shows the current stage (parse, transform, map, rules, address_ai, load, verify) and, during Load, live record counts. Progress counts every record processed -- failed records advance the bar too, and a N loaded, M failed breakdown appears as soon as anything fails, so a run whose records are being rejected still visibly moves.
succeeded Finished with zero failed records. A succeeded dry-run shows N validated -- dry run in the list's Records column instead of a loaded count, since a dry-run never loads anything.
partial Some records loaded and some failed. The records that loaded stay loaded -- use Failed records (below) to re-run just the ones that didn't.
failed Every record failed to load, or the run aborted before reaching Load (a stage error, a rejected source file, the run timeout, or a service restart mid-run) -- nothing was loaded.
cancelled Cancelled from the UI -- records already loaded stay loaded; the run keeps its real partial counts.

The run's Records count (loaded / failed / total, shown in the list and on the detail page) counts parent records, not raw input rows. When a map loads a hierarchical document -- a header with its detail lines, or one source row that expands into several row-type rows -- the whole group is one record (the one document sent to the target), so a file of 100 orders with 500 lines reports 100, not 600. For a flat one-row-per-record file the two are the same.

Once the map has history, an ETA (~2m left) appears while running -- the average of that map version's recent successful runs minus time elapsed; a first-ever run shows no estimate. Cancel (same roles that can launch) appears on queued and running runs: a queued run is cancelled instantly, a running one shows "Cancelling..." for a few seconds while it stops at its next checkpoint. Cancelling is not a failure, so it never fires a failure notification.

Sections that can appear on a run's detail page, each conditional on what the map and run produced:

Section Appears when
Reports & downloads The run is finished and not purged. Load results report (Excel) -- five sheets: a Summary with the run's identity, target config, timings, stage-funnel counts, and success rate; the per-record Load Results; a Failed Records sheet; an Error Breakdown grouping failures by message with counts, share, and sample input rows; and an Issues sheet unioning every parse/transform/data-quality finding. Load results AI export bundle -- the same data as the load-results workbook, but as plain tidy CSVs (load_results, failed_records, error_breakdown, issues -- no styling, no caps) plus a README.md data dictionary and summary.json, zipped: hand its files to an AI assistant (or pandas/Power Query) to analyze the failures directly. Transformation report (Excel) -- how input became output, rebuilt deterministically from the run's retained input file and its compiled map version: a Field Rules sheet (every mapping rule in plain notation), a Transformation Grid showing each source row's values beside the target record it produced (with the producing rule above each target column and the record's loaded/failed/dropped outcome), a Column Analysis sheet (fill rates, distinct counts, top values per target field; which rules use each source column, flagging unused ones), and a Dropped & Failed sheet with the offending source values. Failed records -- see below.
Parse issues / Transform issues Source-contract violations or formula/xref failures happened, listed by row and column.
Load results A real (non-dry) run sent records -- per-record adapter results. Each row shows the record's Input Row (the 1-based data row in your input file), its Row Type (which table of a multi-table map it belongs to, e.g. Sites vs connectionSiteDomains), and a short Record field summary -- so an error like "Field is mandatory" says which record it's about. The summary shows only the record's main unique index fields (e.g. domainCode=10USA, siteCode=10-JOE) when the map or target schema identifies one -- a REST target's vendored Business Document schema, a QXtend target's QDoc schema, or the Source tab's Key Column; a QXtend child table with no unique index of its own (e.g. a line-item table) instead shows the fields that join it back to its parent record -- falling back to the record's first few mapped fields otherwise.
Dry-run preview Dry-runs only: a summary plus downloadable preview_records.xlsx (side-by-side source-to-target columns, a Dropped Rows sheet, an Issues sheet).
Data-quality rules The compiled map has a Rules tab (drop rules remove rows via the failure policy, warn rules only report), or the always-on duplicate destination-key check found two or more records mapped to the same ERP key -- every colliding record is dropped via the failure policy and listed here, even on a map with no Rules tab at all.
AI address analysis The map flags a field as Field Type: address and an Admin turned on Enable AI address analysis and Admin: Client Data LLM Config is set up (address values are real client data, so they only ever go to the client-data connection). Informational only, nothing is auto-applied.
Analyze with AI button Your account has the AI on client data permission (Admin: Users) and Admin: Client Data LLM Config is set up -- run analysis sends failure samples with raw ERP error text, so it uses the client-data connection, not the schema-only one. Never on Excel-target runs (no ERP to spot-check against); on dry-runs it uses the deterministic preview instead of a load precheck.
Data verification The run succeeded (or partially succeeded) against a REST/QXtend target and Extractions are enabled for the domain (Admin/client_admin only) -- a Verify loaded data button plus a table of past verifications. See Verify Loaded Data.
Purge data / Delete run Admin/client_admin/Consultant, once finished (Viewers can only view). Purge removes the input file, output, and reports; delete additionally removes the run record itself (needed before you can delete a map version it still references). Neither can be undone.

A REST/QXtend run that fails on an authentication error stops immediately rather than retrying -- every not-yet-attempted record is marked not-sent, to avoid locking the ERP account on bad credentials. For REST this covers a 401/403 and any QAD response whose error body reports an authentication/session failure ("Authentication failure", "Invalid session ID", ...), which QAD sometimes sends as a plain 400; one automatic re-login is attempted first, so a transient QAD session hiccup recovers on its own. The record (or batch) whose call actually hit the wall shows QAD's own verdict appended to the stop message -- so you can tell a wrong password from an invalid/expired session, an invalid user ID, or an expired token (each has a different fix), along with the QAD correlation ID that matches the failure in QAD's own server logs; records never attempted after it show only the bare stop message. If the run still stops, fix the credentials (the launch prompt for a web-triggered run, or Admin: System Config for CLI/API/watch-folder/scheduled runs) and re-run.

Reprocessing failed records. The Failed records download re-emits the input rows whose records did not load -- transform/rule drops and load-stage failures alike, including records never attempted after a mid-load cancel -- in the same format as the run's input file (same header rows, same csv delimiter or xlsx). Fix the cause, then launch a new run with the same map using that file as the input: only the failures are reprocessed, no hand-editing the original file to remove what already loaded. The Re-run failed records button (same roles that can launch) does exactly that in one click -- it generates the file and launches the new run, with a dry-run checkbox and, for a live re-run of a REST/QXtend map, the same ERP credentials prompt you get when launching a run (used once, never stored; a dry-run re-run skips it). Notes: the re-emitted file is parse-equivalent rather than byte-identical (quoting/encoding normalize; an xlsx keeps only the data sheet's values -- exactly what the run reads); and a run that aborted (abort-on-first-error, threshold exceeded) loaded nothing at all, so there the honest re-run input is the original file -- the download then contains just the rows that triggered the abort, useful for isolating offenders. A run where everything loaded answers with "no failed records to export".

Launching the real run from a dry run. A finished dry run's page shows a Launch real run button (same roles that can launch): one click starts a real load of the same input file against the same map version -- no re-download, no re-upload. It's exactly equivalent to relaunching that file by hand with Dry run unticked, and it queues like any other run. REST/QXtend maps pop the same ERP credentials prompt as launching a run (used once, never stored). The button needs the dry run's retained input file, so it disappears once the run's data has been purged -- relaunch the original file from Launch a Run instead. Automation gets the same promotion via POST /api/runs/{run_id}/launch-real or cimvp launch-real <run-id> (see CLI and REST API).

Verify Loaded Data

Reached from the Verify loaded data button on a succeeded run's detail page (Admin/client_admin, REST/QXtend targets only, and only once Show Extractions and Extract Profiles is on for the domain -- see Admin: System Config). It answers "did the data actually land in the target ERP?" with real evidence: an extraction reads the records back out of the target system, then a comparison diffs that readback dataset against the run's own input file. Delivery is skipped on the readback extraction -- nothing is ever re-loaded.

Field / control What it does
Profile family / Version The extraction profile to read the target back with. A profile whose deliver spec targets this run's map is suggested and preselected -- it reads back the same entity in the same column shape. No suggestion means no compiled profile delivers to this map; pick one manually or author one first.
Client / Overlay version Optional client overlay, same as Launch an Extraction.
Connection The source connection to extract over -- point it at the environment the run loaded into.
Dataset name Which of the profile's datasets to compare (prefilled from the suggestion).
Key column(s) Prefilled with the map's Source Key Column(s) (composite included); comma-separated for a composite key.
Ignore columns / matching options Same options as Launch a Comparison. On duplicate key defaults to Group as child records here, because hierarchical (multi-row-type) inputs legitimately repeat the key across parent and child rows -- grouping compares each child row against its counterpart by position, where the old Warn default silently kept only the last row per key.

Submitting queues the extraction and the comparison back to back on the same sequential worker and returns to the run page, where the Data verification table tracks both live and links to the comparison's counts and Excel report.

Reading the result honestly: Changed rows can be either a load problem or the target ERP normalizing values (defaults, formatting, transforms the map itself applied) -- use Ignore columns to silence known-noisy columns. Added means "in the target ERP but not in this run's input": expected whenever the extraction reads the whole table rather than just the records this run loaded. Removed rows -- in your input but not read back -- are the ones to investigate first.

Admin: Users

Accounts are created here -- there's no self-service signup. Per-account AI Analysis (schema), AI on client data, and Smarter Andy are three separate permissions, independent of role and of each other, all off by default for every account except the very first bootstrap admin. AI Analysis covers the schema-only AI assists (AI field mapping, join suggestions -- table and field names only); AI on client data covers the features that send real client data to the LLM (Analyze with AI on runs, the extraction draft-overlay, MCP access for non-admins) and is the stricter grant -- holding one never implies the other.

Add user:

Field What it does
Email The login identifier.
Password Required in local password mode. Under Entra SSO it's optional -- leave it blank to provision an SSO-only account that activates on first Microsoft sign-in.
Role admin, client_admin, consultant, or viewer. A client_admin can only manage client_admin/consultant/viewer accounts sharing their domain -- never an admin.
Domains One or more; client_admin and viewer roles are limited to exactly one domain. A site Admin (only) also sees a CVLite option here -- see below.
Allow AI analysis on schema / Allow AI on client data / Allow Smarter Andy The three per-account permissions; all off by default.

Per-row: toggle AI (schema) / AI (client data) / Smarter Andy any time; change Domains via the checkbox picker; Reset Password issues a one-time temporary password (their current one stops working immediately); Delete removes the account. Neither reset nor delete can be undone. (A site Admin's own row shows an empty Domains picker -- expected, not a misconfiguration: the admin role reaches every domain implicitly, so there's nothing to pick.)

CVLite sends a Consultant or Viewer to an external "Lite site" (its URL set on Admin: System Config) instead of, or -- for a Consultant only -- alongside, this app. Only a site Admin can assign it, and only to a Consultant or Viewer; Admins and client_admins can never hold it. A Viewer assigned CVLite has no other domain (it's their one allowed slot); a Consultant can combine it with real client domains, and after login picks between them the same way a multi-domain Consultant already picks a domain today. The default for a newly created Consultant or Viewer is CVLite -- pick a real domain instead if that's not what you want. If nobody has configured the Lite site URL yet, a CVLite-only account can't sign in until an Admin sets it.

Bulk import users -- download the Excel import template, fill in Email + Role (dropdown) per row, with optional Domains (comma-separated, blank defaults to your own, or to CVLite for a Consultant/Viewer row in a site Admin's import) and permission columns (blank = off), then upload it. Each new account's temporary password is shown once in the results table -- there's no automated email delivery, so pass it along yourself. Bad rows (invalid email/role, duplicate email) are reported and skipped without failing the rest of the batch.

Users can also change their own password from Account without an Admin's help, any time they know their current one.

Admin: LLM Config

The LLM connection for this domain's schema-only AI assists: AI field mapping (see Create a Starting Map) and Business Object join suggestions. These send table and field NAMES to the LLM, never actual data values. The AI features that do see real client data (Analyze with AI, the draft-overlay assist, AI address analysis, Andy Tool Mode) use the separate Admin: Client Data LLM Config connection instead.

Field What it does
Base URL e.g. https://api.openai.com/v1 or http://localhost:11434/v1. Ollama Cloud (https://ollama.com/v1) requires an API key; only a self-hosted local server works without one.
API key (paste directly) The easy path: paste the provider key straight into the page. Stored server-side, write-only -- it is never shown again and never sent to any browser; a blank re-save keeps it, and an explicit Clear the stored key checkbox removes it. Takes precedence over the env var.
API key env var (alternative) The env var name, never the literal key -- set the real value in the deployment's env file (needs shell access and a service restart). A blank re-save clears the name (it's a name, not a kept secret).
Model e.g. gpt-4o-mini, llama3.1.
Model supports tool calling Whether the model can be asked for structured tool calls.

Test Connection probes whatever base URL / key / model is currently typed into the form -- saved or not -- and reports the result inline without reloading the page or touching the stored config; a blank API key field falls back to the stored key or env var, same as Save.

The LLM call is relayed server-side -- any key is resolved server-side and never sent to any browser. Ask Andy's own connection is configured separately -- see Admin: Andy LLM Config.

Admin: Client Data LLM Config

The LLM connection for this domain's AI features that see real client data: Analyze with AI on runs (failure samples carry raw ERP error text), the extraction draft-overlay assist (source-host probe output), AI address analysis (actual address values), and Andy Tool Mode / MCP (live ERP query results). Set it to the client's own provider and API key when they want their LLM used for their data. There is deliberately no fallback to Admin: LLM Config: until this is configured, those features stay off for this domain -- client data never silently rides the shared connection.

Field What it does
Base URL e.g. https://api.openai.com/v1 or http://localhost:11434/v1. Ollama Cloud (https://ollama.com/v1) requires an API key; only a self-hosted local server works without one.
API key (paste directly) Paste the client's key straight into the page -- stored server-side, write-only (never shown again, never sent to any browser); blank re-save keeps it, the Clear checkbox removes it. Takes precedence over the env var.
API key env var (alternative) The env var name, never the literal key -- set the real value in the deployment's env file. A blank re-save clears the name.
Model e.g. gpt-4o-mini, llama3.1.
Model supports tool calling Needed for Andy Tool Mode and the database spot-checks Analyze with AI can do through MCP servers.

Users additionally need the per-account AI on client data permission (Admin: Users) to reach these features -- the schema-only AI Analysis permission never opens them. The LLM call is relayed server-side by the app's own proxy routes -- the key is resolved server-side and never sent to any browser.

Test Connection probes whatever base URL / key / model is currently typed into the form -- saved or not -- and reports the result inline without reloading the page or touching the stored config; a blank API key field falls back to the stored key or env var, same as Save.

Admin: Andy LLM Config

The LLM connection Ask Andy's chat panel uses for "Smarter Andy" and Teach Me Mode answers -- separate from Admin: LLM Config (run analysis), so Andy can point at a different, typically cheaper model. "Not configured yet" means Smarter Andy and Teach Me Mode won't work for anyone until this is set; Andy's offline help search keeps working regardless.

Field What it does
Base URL Typically an Ollama endpoint -- https://ollama.com/v1 (Ollama Cloud) or http://localhost:11434/v1 (local).
API key (paste directly) Paste the key straight into the page -- stored server-side, write-only; blank re-save keeps it, the Clear checkbox removes it. Takes precedence over the env var.
API key env var (alternative) The env var name, never the literal key; never re-rendered after saving.
Model e.g. gpt-oss:20b, llama3.1.

(No tool-calling checkbox here -- that only matters for run analysis.) The chat call is relayed server-side via /andy/llm-proxy. A user only reaches this connection when an admin has granted them the Smarter Andy permission (Admin: Users) and they've consented in the chat panel to sending their question to the AI.

Test Connection probes whatever base URL / key / model is currently typed into the form -- saved or not -- and reports the result inline without reloading the page or touching the stored config; a blank API key field falls back to the stored key or env var, same as Save.

Admin: System Config

Settings here take effect immediately on save -- no restart needed, and each client domain has its own copy. (Storage paths, host/port, the session secret, and watch-folder mappings still require editing config.yaml and a restart. On startup, each watch-folder inbox is swept once for files already sitting there -- e.g. dropped while the service was down -- so nothing waits for a fresh file-system event to be picked up.)

Group Settings
Appearance One of five dark themes (Electric Cyan, Navy + Gold, Aurora, Monochrome Navy, Greenscreen); purely visual, applies site-wide.
Authentication (site Admin only) Login mode: Local password or Microsoft Entra ID SSO. SSO needs the Entra app registration's Client ID, a Client secret env var name, the Redirect URI (must exactly match what's registered in Entra), and Allowed Entra tenant IDs.
CVLite (site Admin only) CVLite redirect URL -- the external "Lite site" a Consultant/Viewer assigned CVLite on Admin: Users is sent to after login. Site-wide, same as Authentication above. Blank disables CVLite sign-in for everyone assigned it until this is set.
QAD REST Base URL (the Adaptive environment root -- ends in /clouderp, not /api; each map's Target tab supplies the rest of the path). Username/Password env vars are fallback credentials used only by unattended watch-folder runs -- manual launches always prompt instead.
QXtend Tomcat host/port, QXI instance, Use SSL, Request timeout, Domain, Entity, Mnemonics raw, Receiver, plus the same watch-folder-only credential env vars.
Concurrency / API Max concurrency, and the REST API token env var (the CLI/REST API's shared token; an env-var name, not the literal token).
Extractions Show Extractions and Extract Profiles -- reveals those two screens plus Admin: Schedules for Admin/client_admin accounts in this domain. Off by default; Consultants and Viewers never see them regardless.
Ask Andy Enable Teach Me Mode (off by default -- the "teach me" trigger phrase in Andy's chat). Ask Andy Persona (site Admin only) -- which roles get Andy's warm "Italian uncle" personality; an unchecked role still gets full help-search and Smarter Andy answers, just plain and businesslike.
Data quality Enable AI address analysis -- the system-wide switch for the per-run address feature described under Runs. Off by default; a map's own Field Type: address opt-in has no effect until this is also on.

Allowed Entra tenant IDs is the actual security gate for SSO: a sign-in is accepted only if its tenant is in this comma-separated list, and while it's empty, SSO refuses every tenant -- so an attacker's own Azure AD tenant can't spoof a user's email address.

Admin: MCP Servers

Registers the MCP server(s) whose tools are exposed to Analyze with AI. Changes take effect on the next tool listing -- no restart needed.

Field What it does
Name Lowercase slug -- tools are namespaced name__tool so the model knows which server it's calling.
Transport stdio (a subprocess of this service) or sse (a URL).
Address A full command line for stdio, a URL for sse.
Classification inbound = source-side data (e.g. a direct Progress/QAD read); outbound = target-side data (e.g. the QAD REST API). Informational, used for model routing.
Description Shown to the model.
Deny-list Comma-separated tool names blocked outright, regardless of how the server itself flags them.
Env allow-list (stdio only) Environment variable names the subprocess may read (* for everything, blank for none). A stdio server otherwise gets only a minimal safelisted environment -- no app secrets.
Enabled Disable a server without deleting its row.

Admin: Notifications

Best-effort email + webhook alerts for five events -- run failure, extraction failure, AI-verify mismatch, watch-folder error, and run success -- that never block or fail the thing they're reporting on.

Card What it does
Email (SMTP) Host, port, from address, username/password env vars, Use STARTTLS. Recipients are every current Admin-role user's login email -- no separate recipient list to maintain.
Notify on The domain-wide kill switch per event, for both email and every webhook endpoint below -- an event unchecked here never reaches anything, regardless of an endpoint's own settings.
Webhook endpoints Each row: Name (a label for your own reference, not sent), URL, Format, its own Notify on checkboxes, and Enable/Disable/Delete.

Format: Raw JSON (the flat event payload, documented in docs/api/webhooks.md) or Microsoft Teams (Adaptive Card) (the same event data wrapped as an Adaptive Card, for a Teams Workflows "when a webhook request is received" trigger -- that trigger rejects raw JSON).

An endpoint only receives an event when it's checked both at the domain level and on that endpoint's own row -- e.g. to send only run-failure alerts to a Teams channel while other endpoints keep getting everything, leave the domain-wide toggles checked and uncheck the rest on just that endpoint's row. Run success is unchecked by default everywhere -- unlike the other four, it fires on every successful run, so it's opt-in to avoid flooding inboxes and channels.

Admin: Source Connections

Each row is one SSH destination an extraction can run against. A new connection isn't trusted until you click Test Connection.

Field What it does
Host / Port / Username The SSH destination.
Auth method key -- with a Key path and optional Key passphrase env var -- or password, via a Password env var. No secret is ever typed directly.
Connect timeout Seconds before giving up.
Environment An optional label (e.g. TEST/PROD), purely descriptive.
Legacy algorithms Check for older AIX/QAD hosts that only speak legacy SSH kex/key types.

Test Connection pins that host's SSH key fingerprint -- every later connection through this row is verified against that pin, so a host key that changes later is a hard failure rather than a silent re-trust. Only connections with a pinned fingerprint appear on Launch an Extraction.

Scan schema → XSD (once pinned) walks every database the connection can reach and produces a single .xsd -- see Schema Scans for the results page and the no-SSH offline alternative.

Admin: Tunnel

When the ERP server sits on a private network the app server can't reach, a PC on that network runs the downloadable tunnel app: it connects out to this app server and carries QXtend loads and SSH extractions back to the ERP server. Nothing is installed on the ERP server, and the tunnel PC needs no install either -- just unzip and double-click. One machine holds the tunnel at a time; any others running the app stand by (showing who holds it) and take over automatically if the holder drops.

Section What it does
Status Live view: whether the domain's two ports are listening on the app server, which machine (and user) holds the tunnel and since when, and who is standing by. "Reported active but the port is not listening" means the holder just dropped -- a standby machine takes over within moments.
Ports & targets This domain's two reserved ports on the app server (any free pair 1024-65535 -- each domain needs its own; 2222/8080 are refused) and the ERP server's SSH/QXtend addresses as the tunnel PC sees them. Blank both ports to un-configure.
Key & token Generates this domain's restricted SSH key: it can only forward the two ports above -- no shell, no commands. Paste the shown authorized_keys line on the app server once; Generate new key rotates key and heartbeat token together and invalidates every previously downloaded bundle.
Tunnel app download The zip for the client site: the tunnel app, this domain's key, and its config. Available once ports, targets, and a key exist.
Wiring check Shows whether the domain's QXtend settings and each source connection actually point at 127.0.0.1:<tunnel port>. Saving ports never rewrites them for you -- this panel is how you notice they don't match.

If loads or extractions suddenly time out for just this domain, check Status here first: a stopped tunnel PC looks exactly like a broken ERP server from the run's point of view.

Schema Scans

A schema scan walks every database connected to a Progress/QAD session's .pf file (via the standard _file/_field metaschema tables) and produces a single .xsd describing every database, table, and field found -- ready to feed into a Business Object import or reference from an SSH-extract Input Source.

Start a scan with Scan schema → XSD on Admin: Source Connections (the connection must have a pinned fingerprint first). This page lists every scan; a scan's detail page shows the summary and a Download schema.xsd link once succeeded.

Column What it shows
Scan / Connection Which scan, over which pinned source connection.
Status queuedrunningsucceeded / failed, updating live. A service restart mid-scan force-marks the row failed on startup.
Databases / Tables / Fields What the finished scan found -- a quick sanity check that it really walked every database the session's .pf file connects.
Started When the scan began.
  • Download offline scan package -- for hosts where SSH isn't allowed at all: a zip of the same two ABL programs the SSH scan runs (schema_to_xsd.p, schema_dump_db.p) plus a README. Run them by hand from the Procedure Editor and upload the resulting .xsd into a Business Object import afterward -- an offline scan never creates a row on this page.
  • Delete (Admin/client_admin/Consultant) removes the scan record and its .xsd for good -- but only once the scan is finished; a queued or running scan can't be deleted until it succeeds or fails. Expected behavior, not a stuck button.

Admin: Schedules

A schedule fires an extraction automatically on a fixed Every N minutes cadence -- an interval counted from creation or last fire, not a wall-clock time or cron expression, so a brand-new schedule fires on the scheduler's very next check.

Admin/client_admin only, same visibility rule as Extract Profiles -- a schedule fires an extraction, so this page is hidden wherever Extractions/Extract Profiles are.

Field What it does
Name A label for the schedule.
Profile family / Version Which compiled profile to fire.
Client / Overlay version (optional) Selects a client overlay, defaulting to the client's latest version.
Connection Which enabled source connection to run against.
Every N minutes The interval (minimum 1).

A schedule whose previous run is still in progress is skipped rather than piled up behind it -- so "schedule never fires" usually means it's paused, or its extractions take longer than the interval. Pause/Resume control firing without losing history; Delete removes the schedule (past extraction rows are untouched); the inline Edit panel changes any field without recreating it.

Docs updated 2026-08-21 · 3b19536d