Migration Sources

A Migration Source is the contract between the legacy system and MFI Suite — it says 'this file shape, from this system, mapped to these production fields, is acceptable.' Sources are registered once per tenant migration and edited freely until the first Commit, after which they become immutable.

Migration runs
Run history — source, target, counts, errors.
At a glance — Path: MFI Suite -> Migration -> Sources. Group: MFI / Migration Lead. A migration project typically has 3-6 sources: Members, Savings Accounts, Loans, Schedules, Transactions, and GL Opening Balances.

Anatomy of a source

Every Source has a header (who, what, where, file format) and a Mapping Studio (legacy field -> MFI Suite field, with an optional Python transform). The Mapping Studio is where the bulk of the work happens.

  • Header: Name, Legacy System, Template, File Format (CSV/XLSX/Fixed-width/SQL), Encoding (UTF-8/Windows-1252/Latin-1), Date format, Decimal separator, Owner (Migration Lead), Reviewer (Data Steward).
  • Mapping Studio: a table of target_field, legacy_field, transform_python, default_value, required, sample_value. The sample_value column is auto-populated from the first 100 rows of a test upload.
  • Business rules: per-source toggles such as 'Treat negative balance as overdraft', 'Snap deposit dates to month-end', 'Bucket loans older than 5 years into Historical'.
  • Test runner: upload a 100-row sample. The Source shows green only after a sample passes validation.

Registering a Loan Performer source — worked example

Source formats
Mambu / Musoni / Bankers Realm / generic XLSX.

Loan Performer 8.x is the most common legacy system in Kenyan SACCO migrations. Here is the exact recipe.

  1. Open MFI Suite -> Migration -> Sources -> New.
  2. Name: 'Tujikomboe SACCO — Members extract — Nov 2025'.
  3. Legacy System: 'Loan Performer 8.x'.
  4. Template: 'src.loanperformer.v8' (loads the default field map).
  5. File Format: CSV, Encoding: Windows-1252 (Loan Performer's default), Date format: dd/mm/yyyy, Decimal separator: comma.
  6. Owner: Jane Wambui (Migration Lead). Reviewer: Peter Otieno (Data Steward).
  7. Open Mapping Studio. The template prefills 41 fields. Verify these manually because Loan Performer installations are customised:
  8. MemberNo -> source_key (required), Surname + ' ' + FirstName -> full_name (concat transform), IDNo -> id_number, Tel1 -> phone (strip spaces and prefix +254), DOB -> date_of_birth, JoinDate -> joined_on, Branch -> branch_code, Status -> status (transform: 'A' -> active, 'D' -> dormant, 'C' -> closed).
  9. Upload a 100-row test extract. The Test Runner shows 98 green, 2 red — the 2 reds are members with a Tel1 of 'NIL'.
  10. Add a transform to coerce 'NIL' to empty string. Re-run test. 100 green. Source is now 'Verified' (green badge).
Note — Save the Source. It can still be edited. Lock it by clicking 'Freeze Mapping' once Data Steward has signed off.

File formats and their gotchas

MFI Suite accepts five file formats. Each has Kenya-specific gotchas worth flagging at source-registration time, not at validation time.

FormatMax sizeCommon gotchas in Kenya
CSV (UTF-8)500 MB per file, unlimited rowsLoan Performer exports as Windows-1252; Co-op exports as UTF-8 with BOM. Pick the right encoding or member names with accents (Wanjirũ, Akinyi) will corrupt.
CSV (Windows-1252)500 MBDefault for Loan Performer. Date format almost always dd/mm/yyyy.
XLSX120 MB, 1,048,576 rows per sheetExcel silently coerces 10-digit member numbers to scientific notation; always import as text. Multiple sheets allowed — one source per sheet.
Fixed-width TXT500 MBUsed by some older Bankers Realm exports. Specify column offsets in the Mapping Studio.
SQL extract (via SFTP drop)2 GB compressedFor tenants where IT can run SELECT INTO OUTFILE; importer reads the file from /opt/staging/migration/inbox//.

Mapping Studio transforms

Each mapping line accepts an optional Python transform. The expression has access to value (the raw legacy value) and row (the entire legacy row as a dict). The result must be a string for text targets, a date for date targets, or a float for monetary targets.

# Normalise Kenyan phone numbers to +254 format
value = (value or '').strip().replace(' ', '').replace('-', '')
if value.startswith('0'):
    value = '+254' + value[1:]
elif value.startswith('254'):
    value = '+' + value
elif value.startswith('7') and len(value) == 9:
    value = '+254' + value
result = value or False

# ----
# Concatenate Loan Performer surname + first name into full_name
result = (row.get('Surname', '') + ' ' + row.get('FirstName', '')).strip().title()

# ----
# Coerce Loan Performer status flag to MFI Suite enum
map_ = {'A': 'active', 'D': 'dormant', 'C': 'closed', 'B': 'blacklisted'}
result = map_.get(value, 'active')
Warning — Transforms run in a sandboxed exec() with no network or filesystem access. Anything that raises an exception aborts the row and produces a TRANSFORM_FAIL exception. Test transforms on the 100-row sample.

Freezing and versioning a source

Once the Data Steward signs off on a source mapping, click 'Freeze Mapping'. The mapping becomes read-only and a version number (v1, v2, ...) is stamped on every staging row that uses it. If the legacy system later produces a slightly different extract, the operator does not edit the frozen source — they clone it ('Copy as new version'), tweak, and freeze a v2. This keeps lineage clean.

Worked scenarios

Scenario — Tujikomboe SACCO registers six sources for go-live

Setting: Tujikomboe SACCO in Eldoret is moving 8,400 members and 3,100 loans from Loan Performer 8.x to MFI Suite. Go-live is set for 2026-01-15.

CharacterRole
Jane WambuiMigration Lead (BridgeERP consultant)
Peter OtienoData Steward (Tujikomboe Operations Manager)
Aisha HassanTujikomboe IT (extracts the CSVs from Loan Performer)
Florence AchiengFinance Reviewer (Tujikomboe Accountant)

Timeline

  1. Day 1, 09:00: Jane creates Source 'Tujikomboe — Members — v1' using template src.loanperformer.v8. (Source state: draft.)
  2. Day 1, 11:30: Aisha drops a 100-row sample CSV via the Test Runner. (Test report: 87 green, 13 red — all 13 reds are members with Tel1='NIL'.)
  3. Day 1, 14:00: Jane adds a Python transform on phone: coerce 'NIL' to False. (Sample re-runs: 100 green.)
  4. Day 2, 10:00: Jane registers five more sources: Savings Accounts, Loans, Loan Schedules, Historical Transactions (savings), GL Opening Balances. (Six sources, all draft.)
  5. Day 3, 09:30: Peter spot-checks the Mapping Studio for each source and adds two more transforms (date-of-birth NULL coerced to 1900-01-01 sentinel, branch code 'HQ' coerced to 'NAIROBI-CBD'). (All six sources reach 100% green on samples.)
  6. Day 3, 16:00: Peter signs off; Jane clicks 'Freeze Mapping' on all six. (Sources move to state 'frozen-v1'. Staging tables ready to receive.)
  7. Day 5, 08:00: Aisha discovers Loan Performer was upgraded mid-week and now exports loan_outstanding as a separate column. (Jane clones to v2, adds the new column, freezes. v1 remains for audit.)

Outcome — Six frozen sources, fully mapped, with v1 and v2 lineage. Staging uploads can now begin. Total elapsed: 5 working days, well inside the 2-week budget.

Reference

Source states

A Source moves through six states. Only Frozen sources can feed a Migration Run.

StateMeaningEdit allowed?Can feed a Run?
draftInitial state, mapping in progressYes (Lead)No
sample_pendingAwaiting test uploadYes (Lead)No
sample_failedTest upload has unresolved errorsYes (Lead)No
sample_passed100% of test rows validateYes (Lead)No
frozenMapping locked by Lead after Steward sign-offNoYes
supersededCloned into a newer versionNoNo (read-only audit copy)

Default mapping for src.loanperformer.v8 (Members)

Every line is editable. Required lines cannot be removed without breaking validation.

Target fieldLegacy fieldTransformRequired
source_keyMemberNovalue.strip()Yes
full_nameSurname + FirstNameconcat & title-caseYes
id_numberIDNovalue.strip().lstrip('0')Yes
phoneTel1+254 normaliserNo
date_of_birthDOBparse dd/mm/yyyyNo
joined_onJoinDateparse dd/mm/yyyyYes
branch_codeBranchvalue.upper()Yes
statusStatusenum map A/D/C/BYes
kra_pinKRA_PINvalue.upper().strip()No
genderSexM->male, F->femaleNo
next_of_kin_nameNokNamevalue.title()No
next_of_kin_phoneNokTel+254 normaliserNo

Troubleshooting

SymptomLikely causeFix
Test Runner says 'Encoding mismatch' on every row.Loan Performer wrote the file as Windows-1252 but the Source is set to UTF-8.Open the Source, change Encoding to Windows-1252, re-run test.
Transform fires successfully on the sample but a real upload shows TRANSFORM_FAIL.Real data contains an edge case the 100-row sample did not (e.g. a member with no DOB).Wrap the transform body in a try/except that returns False on failure, re-freeze as v2.
Cannot freeze the Source — button stays disabled.At least one required mapping line is empty, or no Data Steward has been assigned.Open the Mapping Studio and check that every Required=Yes row has a legacy_field set; ensure Reviewer is filled.
Member names with diacritics (Wanjirũ, Akinyi) appear as 'Wanjir~' after upload.Wrong encoding chosen at Source registration.Reopen the source CSV in a text editor that displays the encoding; usually Loan Performer is Windows-1252 and Co-op is UTF-8. Pick correctly.

See also

Was this page helpful?