Migrate Custom Reports Copy link
Plugin: Blitz Report Development (
migrate-custom-reports)Works with: Claude Code
Phase-2 engagement workflow for migrating a customer's gathered custom Oracle EBS reports (the Blitz Report Custom Reports Converter zip output) into Blitz Reports — inventory, extract SQL, classify each report into one of three outcomes (complete map / enhance / new custom build), build/recommend, categorize as "Custom Reports <ABBR>", and prove each report prints what the original prints (field inventory + value fidelity gate) on the conversion instance.
Claude Code
- Add the Enginatics marketplace in Claude Code.
/plugin marketplace add enginatics/ai-skills - Install the plugin.
/plugin install blitz-report-ai-skills@enginatics - Run the environment setup once; it describes your EBS development instance (never production).
/blitz-report-ai-skills:blitz-environment-setup - Run the skill, or just describe the task and Claude picks it up.
/blitz-report-ai-skills:migrate-custom-reports - Keep it current: enable auto-update for the enginatics marketplace, or update manually.
/plugin marketplace update enginatics
Requirements: Blitz Report installed on your Oracle EBS development instance, and database access from Claude Code to that instance (Oracle SQLcl MCP server, SQLcl or SQL*Plus). Never point the skills at production.
When to use
A customer has run the Blitz Report Custom Reports Converter Blitz Upload on their instance and handed over the gathered zip (CONV_<ABBR>_<reqid>_<timestamp>/). This skill takes that zip and turns each custom report into a recommendation or a built Blitz Report, in a customer-specific category.
Read custom-reports-migration skill first — it holds the durable taxonomy, the gathered-folder layout, the gotchas, and the category-naming rule this skill depends on. For the per-report RDF/BIP→Blitz mechanics (SQL consolidation, anchors, assignments, parameter binding), delegate to the convert-bip-and-rdf-reports-to-blitz skill.
Arguments
$ARGUMENTS should contain:
- The customer abbreviation (e.g.
ABC) — drives the category nameCustom Reports <ABBR>. - The path to the gathered zip OR an unzipped folder (e.g.
.../CONV_ABC_72146542_20260527_163231.zipor the extracted folder). A zip is fine — unzip it first (step 0). - Optionally, a single program name to process just one report.
Instance
Run against the conversion instance (see blitz-environment.md for how to reach it, connect as apps/$APPS_PWD, copy files to it and query it). Never use any other instance for a conversion.
Workflow
1. Inventory the gathered package
- Step 0 — if
$ARGUMENTSpoints at a.zip, unzip it to a working directory first (e.g. the scratchpad), then operate on the extractedCONV_<ABBR>_<reqid>_<timestamp>/folder. A folder path is used as-is. The customer typically sends the gather zip plus the converter result Excel (which lists the programs they selected) — use the Excel to confirm scope. - Read
manifest.txtandgather_summary.txt— list each program, its execution method (P=Reports, I=PL/SQL, Q=SQL*Plus, H=Host, K/J=Java), and which artifacts came through. - Note, per program, whether
<PROG>_rdf.xmlis present (rwconverter succeeded) and whether any<OWNER>.<NAME>.<type>.sqlcustom-dependency files exist. - Noetix detection: a
noetix/_noetix_conversion.txtmarker in an item's folder (program typeNoetix VieworExcel4appsin the manifest) means this is a Noetix conversion — route it through the Noetix path in step 4 (seecustom-reports-migrationskill → "Noetix conversion"). Thenoetix/subfolder holds the recursiveNOETIX_SYSview/package closure;<PROG>_wand.sql(Excel4apps items) holds the consumer report SQL. - Two-step extract→report detection: a PL/SQL wrapper/data-extract program gathered alongside a BIP/report child (manifest shows e.g.
XXCANX4RRW|Data Extract|...next toXXCANX4RR|BI Publisher|...), both folders sharing the SAME custom staging-table dependency closure, and the wrapper package spec exposing a conc-programMAINentry + a populate procedure + a submit-child procedure — pair them and plan ONE combined conversion, not two (seecustom-reports-migrationskill → "Two-step extract → temp table → report processes"). - 🚨 Request the reference outputs NOW, with the scope confirmation: one output of each original program from the customer's system (Excel, CSV or the XML/PDF it produces), with the parameters it was run with and the run time. It is the arbiter of the value fidelity gate (step 6): printed columns, order and headings, row grain, and every value the file lets you recompute. A report without one can still be built, but its delivery email says that the figures are unverified until the customer compares them. Our own earlier Blitz output is never a reference.
2. Per report — gather the facts (do NOT skip the .ldt or .rtf)
- Parameters & value sets — the AUTHORITATIVE source is
<PROG>.ldt, NOT the SQL binds. Build the full parameter inventory per the rule's 🚨 Parameter fidelity section (Step P1): for each LDT parameter captureFORM_LEFT_PROMPT(→ Blitz name),FLEX_VALUE_SET_NAME(→ LOV),REQUIRED_FLAG,DISPLAY_FLAG,DEFAULT_TYPE/DEFAULT_VALUE, andSRW_PARAM(→ the RDF bind/token, the authoritative mapping). Never derive parameters from the rwconverter SQL bind list — that drops PL/SQL-driven parameters and invents wrong ones. - Data-model SQL:
- If
<PROG>_rdf.xmlpresent → parse the data-model query from it. Also read its<programUnits>/ formula columns / triggers — parameters used only there (not in the SELECT binds) are still real parameters (Step P3). - If absent → rwconverter segfaulted (large/complex RDF). Strings-extract the RDF (
strings -n 8 <PROG>.rdfor a printable-char split) to identify the report and its tables. Do not expect a clean single SQL from a procedural multi-cursor RDF.
- If
- Output columns: read the
.rtf<?FIELD?>codes — the columns the customer's layout actually renders — together with the heading printed over or beside each one: the Blitz column must show that heading verbatim (rule "Data-model SQL", headings bullets). (_ds.ldtis BIP template registration only — no SQL.) 🚨 Field inventory — MANDATORY, before writing any SQL:
python field_inventory.py <program folder>writes<PROG>_field_inventory.md. It reads the_rdf.xml(or the.rexwhen rwconverter failed), the data template, the RTF and the custom package bodies, and lists:- every printed field and where the original computes it (query column, formula, placeholder, summary, parameter, or the package variable the loop writes, with the lines that assign it);
- every formula, with the placeholders it assigns, its
select intocount and its exception handlers; - everything that decides which rows print (group filters, parent-child links, format triggers that can return FALSE, layout conditions and loops, and the triggers that build lexicals);
- each query's
union/union allcounts andorder by.
The query is only part of the report. Most conversion defects sit in the per-row code after it: package loop code filling cursor literals, formula columns, a group filter hiding a section. The inventory is the conversion's work list. Fill its Blitz expression column as you build, and keep the file with the deliverables. A field with no Blitz expression is an open defect, not a simplification.
- Custom objects: list the
<OWNER>.<NAME>.<type>.sqldependency files. - Value sets: the
.ldtembeds the fullVALUE_SETdefinitions. For the Blitz parameters useLOV Oracle+LOV_NAME=<value-set name>(Step P2) — reuse the existing Oracle value set, do NOT re-author LOV SQL. To test on the conversion instance, load the value sets there (standalone value-set LDT) soLOV Oracleresolves. - 🚨 Lists of values — REUSE, in this order: shared Blitz Report LOV → the program's Oracle value set →
LOV customLAST (rule Step P2a). For every list-bearing parameter, query the shared catalogue for the master table it picks from (xxen_report_parameter_lovs_v…dbms_lob.instr(lov_query,'<table>')>0) and record which shared LOV you considered. The shared LOVs carry security you will not reproduce by hand (525 shared LOVs on an R12.1.3 instance: 24 GL-access-set aware, 33org_access_viewaware, 13 MOAC aware). When id-ness forces a custom LOV, re-author only the SELECT LIST and copy the shipped LOV'swhereclause verbatim — improvising it ships an unrestricted list of every ledger, or under-returns by missing ledger-set expansion.
3. Classify into one of three outcomes
Apply the evidence-based 3-outcome taxonomy from the rule (custom-reports-migration skill → "The 3-outcome classification"):
- Outcome 1 — complete map: stock value sets + stock tables + no custom-dependency files ⇒ a standard Oracle report an existing Blitz Report already covers. Identify the matching Blitz Report by the standard program/tables.
- Outcome 2 — partial map: an existing Blitz Report covers most of it but lacks columns/filters ⇒ propose enhancing it after user confirmation; if declined/infeasible, fall through to Outcome 3.
- Outcome 3 — new custom report: bespoke SQL / custom objects / customer-specific columns / row-security ⇒ build new.
Classify by evidence, not by program name (customers rename standard programs).
🚨 MANDATORY GATE — run classify_probe.py BEFORE writing down any outcome
A column-name comparison is not a classification. Column overlap tells you the two reports display similar things; it says nothing about whether the shipped report can produce the customer's rows. The only thing that decides that is the data sources. Do not reason about this from memory or from the column list — run the probe and paste its output into the engagement notes.
# 1. dump the candidate shipped report's SQL from the BASE TABLE (xxen_reports_v truncates at 32764 bytes)
# select xr.sql_text from xxen_reports xr, xxen_reports_v v
# where xr.report_id=v.report_id and v.report_name='<shipped report>'; -> shipped.sql
# 2. run the probe with EVERY customer prefix you have seen, not just manifest.txt's
python ${CLAUDE_PLUGIN_ROOT}/skills/migrate-custom-reports/classify_probe.py \
--custom _extract/<PROG>_datamodel_1.sql --shipped shipped.sql --prefixes XX,XXCIt compares localization objects (JAI_*/JA_IN_*), customer-owned objects (by prefix), standard EBS objects
and union-branch counts, then applies a deterministic verdict (exit code 3 = Outcome 3):
| signal | verdict |
|---|---|
| a localization object the shipped report does not reference | Outcome 3 |
| any customer-owned object referenced by the custom SQL | Outcome 3 (and confirm its DDL was gathered) |
| materially more union branches than the shipped report | Outcome 3 — investigate |
| none of the above | Outcome 1/2 candidate — now do the column diff |
Do not override the verdict with judgement. Example: a custom GL detail report called a two-column enhancement of GL Account Analysis from column names alone; the probe on that exact pair reports 3 localization tables,
1 customer package and 9-vs-3 union branches — Outcome 3.
The probe also prints an UNCLASSIFIED list (identifiers matching no known prefix). Check those by hand — an unrecognised customer naming convention is exactly how a customer package goes missing from the gather.
Build helpers
field_inventory.py <program folder> [<PROG>] [--out <file>]writes the field inventory of step 2. It reads_rdf.xml,.rex(throughrex_parse.py), data templates, RTF layouts and custom package bodies.rex_parse.py <folder> <PROG>decodes a.rextext export when rwconverter fails, writing each query's SQL and the PL/SQL units next to it.vs_parse.py(run inside a program's folder) prints every gathered value set: validation type, table, id and value columns, where clause. Use it to pick the shared LOV that matches each parameter.build_xml.py <spec.py> <out.xml>writes the import XML. The spec definesCATEGORYandREPORTS, a list of report dicts (NAME,DESCRIPTION,SQLfile,GUID,ASSIGNMENTS= the converter's_assignments.xml,PARAMS, andSHORT_NAMEandPROGRAMof the program the report replaces, written as the description's last linesSource:andShort Name:), so one call writes the category deliverable with every report and all assignments; a single report at top level still works. APARAMSrow takes name, anchor, sql (omit for a:bindanchor), typeLOV/LOV Oracle/LOV custom/Date/Char/Number,lovorlov_query,required,default,matching,hidden, andsubfor a further anchor of the parameter above. Shared LOVs are copied with their fallback payload from the shipped report XMLs (content/r12/reportsin the Blitz Report repository, or the folder named by the environment variableBLITZ_CONTENT_DIR, e.g.xxen_api.export_reportexports of shipped reports using those LOVs); the build stops when a parameter has other than one display row or ann=nanchor is not unique in the SQL. Keep the spec and SQL files next to the category file so a re-issue rebuilds from them.- A report cloned from a shipped one (an aging, a trial balance) is built by transforming the shipped XML instead: new GUID, name, description (ending with the
Source:andShort Name:lines), category and assignments, extra columns inserted at the level where their keys are, extra parameters appended. Invoice-level extras go into the detail lexical asmax(x1.col)so summary mode is untouched.
4. Build / recommend per outcome
- Outcome 1: recommend the shipped Blitz Report — do not ship a clone by default (one-version-of-truth; a per-customer copy of a standard report is maintenance debt). To prove equivalence, you may build a throwaway clone —
xxen_api.export_report(<report_id>)→ changeGUID,REPORT_NAME, replaceCATEGORY_ASSIGNMENTSwithCustom Reports <ABBR>→xxen_api.import_xml→ confirm identical SQL + parameter count + behavior → then delete it withxxen_api.delete_report(<id>,'Y'). Only keep a clone if the customer explicitly insists on an isolated/branded copy.- 🚨 The Outcome-1 DELIVERABLE IS A TEMPLATE, not a report. "Recommend the shipped report" is not the whole job — the customer's users expect their column set, order and layout. Ship one named template per replaced custom report (
<ABBR> <original report's user-visible name>, e.g.ABC Division Wise Trial Balance) on the shipped report, so switching is a template pick rather than retraining. - Templates import STANDALONE — never round-trip the whole report to add one.
xxen_api.export_reportreturns an empty<TEMPLATES>section, so re-importing an exported report would DELETE the shipped templates. Use the template-only shape (xxen_api.export_template(<template_id>)produces it): aREPORTS_ROWcarrying only<GUID>(the report's GUID) +<TEMPLATES>, with noSQL_TEXT. That cannot touch the report definition. (A delta with other report elements but noSQL_TEXTraisesORA-01407— seereport-developmentskill.) - Before building a pivot template, check what the shipped report already exposes. Several shipped reports already pivot generically — e.g.
GL Trial Balance - Detailhas a Pivot Segment parameter (&lp_pivot_segment_name, resolved fromfnd_id_flex_segments_vl) plusPIVOT_SEGMENT_VALUE/ACCOUNT_SEGMENTcolumns and a shippedTrial Balance Pivottemplate. A custom RDF that hand-codes one column per division/unit maps onto it exactly: put the account onROW, the division onCOLUMN, the balance onVALUE. That turns N hardcoded columns into data, so a new division needs no code change. - Pivot template XML:
<TEMPLATE_PIVOT><TEMPLATE_PIVOT_ROW>withCOLUMN_NAME,FIELD_TYPE(ROW/COLUMN/VALUE/FILTER),DISPLAY_SEQUENCE, andAGGREGATION(SUM…) onVALUErows. Every column referenced inTEMPLATE_PIVOTmust also appear inTEMPLATE_COLUMNS. Model on the templates of the shippedMSC Horizontal Planreport. - Verify the pivot actually materialised, not just that the request completed: the output
.xlsxmust containxl/pivotTables/pivotTable1.xmlandxl/pivotCache/…(unzip -l <file>.xlsx). A run can finish Normal and still emit flat data if the template was not applied.
- 🚨 The Outcome-1 DELIVERABLE IS A TEMPLATE, not a report. "Recommend the shipped report" is not the whole job — the customer's users expect their column set, order and layout. Ship one named template per replaced custom report (
- Outcome 2: enhance the existing Blitz Report per the standard report-modification workflow (
development-workflowskill → "Standard Workflow for Modifying an Existing Report") — baseline, change, redeploy, regression-diff. Plus a named<ABBR> <original report name>template listing the custom report's columns in the custom report's own order (TEMPLATE_COLUMNS.DISPLAY_SEQUENCE= the RTF/layout field order), so the enhanced shipped report reproduces the customer's familiar layout.- Measure the delta before proposing an enhancement. Diff the custom report's real output columns (the
.rtf<?FIELD?>list, or the RDF formula-column names) against the shipped report's columns across all its templates (xxen_report_template_columns), not just one. Column names differ freely (SOURCE→SOURCE_NAME,TRAN_NO→TRANSACTION_NUMBER,PARTY_NAME→VENDOR_OR_CUSTOMER_NAME), so build a name-mapping table. - 🚨 A COLUMN-NAME MATCH IS NOT A CLASSIFICATION. You MUST also compare DATA SOURCES before calling anything Outcome 1 or 2. Column overlap says the two reports display similar things; it says nothing about whether the shipped report can produce the customer's rows. Always check, in the shipped report's
sql_text: - localization coverage — count
jai_/ja_in/indiareferences. Most shipped reports have none; - subledger/source coverage — which sources the branches actually cover vs the custom report's
UNION ALLbranches; - customer-owned objects — any
<prefix>_*package/view the custom SQL calls, which the shipped report by definition does not. Example: a custom GL detail report was classified Outcome 2 ("onlyPO_NUMBER+ receipt number missing") purely from column names. Checking the SQL showed the shippedGL Account Analysishas 0jai_references and 2 union branches, while the custom report has 8 branches (Inventory / Payables / Receivables / Payables India / Purchasing India / Register India / VAT India), 4 India Localization tables and 25 calls to a customer package. It was Outcome 3 all along. - 🚨 Read
sql_textfrom the BASE TABLExxen_reports, notxxen_reports_vwhen measuring — the view truncates the CLOB at 32,764 bytes, which silently makes two different reports look the same length and hides content past the cut. (GL Account Analysisis 61,606 bytes; the view showed 32,764.) - Only add columns that are GENERIC. A column that is standard EBS data (a PO number, a receipt number) is a legitimate shipped-report enhancement. Customer accounting policy baked into the report — provision percentages, entity-specific exception lists, a particular
gl_daily_ratesconversion type, hardcoded division codes — must NOT go into the shipped report. Offer the customer the choice explicitly: (a) parameterise it so the shipped report stays neutral, or (b) build a separateCustom Reports <ABBR>report. This is a decision for the customer, not a default — ask. When they choose (b), the custom report's<DESCRIPTION>must carry a caveat pointing users to the shipped report as well, so the two are not mistaken for alternatives.
- Measure the delta before proposing an enhancement. Diff the custom report's real output columns (the
- Outcome 3: first create the gathered custom-dependency DDL on the instance (tables may be empty for a parse test), then convert the report via the
convert-bip-and-rdf-reports-to-blitzskill, categoryCustom Reports <ABBR>. Convert the report, not just its query: the data-model SQL plus every row of the field inventory, with the edge behaviour listed inreferences/value-fidelity.md. That covers what a lookup prints for no row or several, widths,nvldefaults, dates as text, row order and grain, serial numbers and report modes. Keep each query's own set operator:unionwhere the original hasunion, over the original's key.- 🚨 Dynamic SQL in the source → lexical anchor, never a frozen copy. A program that assembles part of its SQL at run time (a select list from a lookup or setup table, a FROM/WHERE fragment chosen by a parameter, a hint, an order by) keeps that behaviour: spec entry
{'name': 'Item Columns', 'anchor': '&item_columns', 'sql': <select returning one line per fragment row>, 'type': 'Char', 'hidden': True, 'default': '<lookup type>'}with&item_columnsat the fragment's position in the SQL; for a column list the select returns<expression> <alias>per row with a comma on every row but the last, and the FROM must carry the aliases the stored expressions use. Mechanics, limits and the two harness traps are in the rule's "Data-model SQL" bullets (custom-reports-migrationskill). Writing the gathered day's column list into the SQL is a regression the customer will find the first time they change the lookup. - 🚨 Design every parameter's anchor per rule Step P3 BEFORE authoring the XML — bind anchors are the exception, never the default. A WHERE filter (plain equality, LIKE, or a self-referencing
nvl/DECODEno-op) becomes a1=1anchor with the predicate inSQL_TEXT(Yes/No no-ops becomeYes_Nomatching-value rows). A:bindanchor is allowed ONLY when the token's every use in the main SQL is SELECT-list/CASE/row-security logic that must stay inline — and such a parameter still needs an id/value LOV instead of raw id entry. Write down the justification per retained bind; you will need it for the step-6 gate.
- 🚨 Dynamic SQL in the source → lexical anchor, never a frozen copy. A program that assembles part of its SQL at run time (a select list from a lookup or setup table, a FROM/WHERE fragment chosen by a parameter, a hint, an order by) keeps that behaviour: spec entry
- Two-step extract→report pairs (Outcome 3, ONE report for both programs): convert wrapper + child into a single self-contained Blitz Report using the report-level
<DB_PACKAGE>before/after-report trigger mechanism (shipped reference: FA Depreciation Projection →XXEN_FA_FAS_XMLP). Main SQL = the child's data-model SQL over the staging table keyed to the Blitz request; DB package = the gathered wrapper package adapted (keep populate, drop child-submit and immediate delete, cleanup inAFTERREPORT; public variables named after the bind anchors; APPS-owned); parameter fidelity runs against the WRAPPER's LDT (it is the user-facing program — the child's REQUEST_ID parameter disappears). Full recipe + engine contract + run-context caveats:custom-reports-migrationskill → "Two-step extract → temp table → report processes". Only fall back to the child-only conversion (REQUEST_ID parameter, customer keeps their wrapper) when the customer explicitly wants to keep the extract program. - Noetix items (always Outcome 3 — never install the NOETIX_SYS views): start from the consumer SQL (
<PROG>_wand.sql, or the root view named innoetix/_noetix_conversion.txt), inline thenoetix/view stack bottom-up into nested subqueries, and apply the transform catalogue incustom-reports-migrationskill → "Noetix conversion" (#wrappers → base tables, ACL map views →hr_operating_units/gl_ledgers+ MOAC,xxk_gl_acct→gcck+&segment_columns,noetix_env_pkg.get_language→userenv('lang'), dropA$*/copyright columns, …). Parameters are designed, not converted — Noetix views have none; pick from the standard Blitz parameter/LOV library per the rule's "Phase-2 parameters" section, mirroring the nearest shipped Blitz report of the same domain. Description must state "Migrated from NoetixViews view NOETIX_SYS.<VIEW>". - 🚨 Assignments (all built/cloned reports) — CONSUME the gathered file, NORMALISE the level code, then GATE it offline. Without
<ASSIGNMENTS>no responsibility can see the report. Three rules, each of which has produced a real defect:- Consume
<PROG>_assignments.xmlverbatim — never hand-list groups. It is the authoritative set. Carry every row and keep each row's own<APPLICATION_SHORT_NAME>(they are not all the customer's custom app — expectJA,SQLAP,AR,JE,SQLGLalongside it). Add<REPORT_NAME>per row and place the block as a sibling of<REPORTS>(nesting it insideREPORTS_ROWsilently drops everything —report-developmentskill "Report Assignments"). - Normalise
<ASSIGNMENT_LEVEL>to what the TARGET instance's build expects. Packages gathered before the converter fix carry the obsolete single letter, and an unrecognised code is silently skipped on import (zero rows, no error). Map through the legacy table incustom-reports-migrationskill —S→Site,A→Application,G→Request Group,R→Responsibility,U→User,F→Form,D→Report;Functionhas no legacy letter. Builds ≥21-Jun-2026 need the full words. - Gate it OFFLINE against the gathered file — an instance count of 0 is UNVERIFIABLE, not passed. On a conversion instance the customer's request groups don't exist, so the import legitimately reports zero and proves nothing. Diff the built XML's rows against the gathered file (count, level value, application short name, level code) with no DB at all. This is what let defect 1 ship: the zero was explained away instead of checked.
- Consume
- Description (all built/cloned reports): set
<DESCRIPTION>(insideREPORT_TRANSLATIONS_ROW) to record the source Oracle report (<PROG>+ its user-visible name) and a brief, plain-language summary of what the report does (entity, key columns, main filters). Break it into short paragraphs (perreport-developmentskill). See the rule's "Description — MANDATORY" section. (A pure Outcome-1 recommendation builds no report, so nothing to describe.)
5. Category — always Custom Reports <ABBR>
Every produced report (clone or new) gets exactly this category. No exceptions.
6. Import + validate — the verification gate (rule Step P4)
Import via
import_xml(which derives parameter and upload dependencies from any$flex$/$column$references automatically).🚨 Re-issuing a new version of an already-imported report → DELETE then import fresh (
xxen_api.delete_report(xxen_report.report_id('<name>'),'Y'); commit;thenimport_xml). Importing on top upsert-merges bySORT_ORDER; if the SORT_ORDER→parameter mapping changed between versions the merge is partial (classic symptom: a1=1/&lexicalanchor parameter whoseSQL_TEXTcame out blank — impossible from a clean load). Seereport-developmentskill "Re-issuing a new VERSION".Post-import: assert no anchor parameter lost its SQL_TEXT —
select parameter_name,anchor from xxen_report_parameters_v where report_id=xxen_report.report_id('<name>') and (anchor like '%=%' or anchor like '&%') and nvl(dbms_lob.getlength(sql_text),0)=0;must return zero rows.Post-import: assert assignments landed —
select assignment_level,include_exclude,level_value from xxen_report_assignments_v where report_id=xxen_report.report_id('<name>');must return the expected rows (zero usually means<ASSIGNMENTS>was nested insideREPORTS_ROWinstead of top-level, orREPORT_NAMEwas missing — seereport-developmentskill "Report Assignments").Parse-validate on the instance.
🚨 A wide-open parse+run is NOT acceptance. Before declaring done, produce the parameter-by-parameter fidelity table (rule Step P4) and confirm EVERY row passes: count = LDT count, names =
FORM_LEFT_PROMPT, every value-set parameter hasLOV Oracle+ correctLOV_NAMEand the LOV returns rows, required flags match, hidden/defaults match, every parameter is wired (bind/SQL_TEXT/lexical) so it actually filters.🚨 Bind-anchor gate (mechanical, run the query — never eyeball):
select xrpv.parameter_name, xrpv.anchor from xxen_report_parameters_v xrpv where xrpv.report_id=xxen_report.report_id('<name>') and xrpv.anchor like ':%';— for every returned row, quote the P3-exception justification recorded in step 4 (bind used ONLY in SELECT-list/CASE/row-security logic). Automatic FAIL and re-do: the bind token appears in a WHERE filter predicate, or a retained numeric-id bind parameter has no id/value LOV.🚨 Shared-LOV reuse gate (mechanical, run the query):
select xrpv.parameter_name, dbms_lob.substr(xrpv.lov_query,300,1) from xxen_report_parameters_v xrpv where xrpv.report_id=xxen_report.report_id('<name>') and xrpv.lov_query is not null;— this returns only hand-written LOVs (aLOV Oraclevalue set and a shared-LOV reference both storelov_querynull). For each row, state the shared Blitz LOV you considered and why it was rejected — id-ness or genuine non-portability are the only valid reasons. Automatic FAIL: the LOV selects from a security-scoped master table (gl_ledgers,hr_operating_units,hr_all_organization_units,mtl_parameters, …) with no security predicate, or with one improvised rather than copied from the shipped LOV.E2E with REAL filter values — through the Blitz frontend run screen (it evaluates hidden defaults), or with real filter values set. Confirm the filters actually restrict the output. Never accept a wide-open/default-only run as proof.
API hidden-default limitation (rule → "E2E via the bare API…"):
ORA-01741/ORA-01861under the bareset_parameter_value_API are harness limitations, not report defects. For an Outcome-1 clone, equivalence to the shipped report (identical SQL + parameter count + behavior) is sufficient.🚨 Value fidelity gate — the acceptance test, for the first delivery AND every correction (full procedure and harness skeletons:
references/value-fidelity.md). Every other gate in this step proves the report is well-formed, not that it prints what the original prints. A report that only parses, runs and returns rows is not delivered. All six must pass:- Inventory complete: every row of the field inventory has a Blitz expression or implementation, or a reason from the source why it never reaches the output.
- Formula port: the original's formulas and per-row package code, ported as static PL/SQL, run on every row of the Blitz SQL, with 0 differences on every column. Where the instance lacks the data (India Localization lines, the customer's own tables), feed identical stand-in rows to both sides.
- Reference output: the customer's ERP output matches on columns, order and headings, every recomputable relation holds on every row, and the row grain and order match.
- Old against new for a correction: 0 differences on every column the change does not intend to alter.
- Full-fetch cost: old and new, every column, over the same window.
count(*)prunes columns and proves nothing about their cost. - Independent audit before the first delivery: a second agent with only the original's files and the Blitz XML (
references/fidelity-audit-prompt.md). Every DIFFERENT or MISSING is fixed or answered from the source before the file goes out.
Record each result in the inventory's Proof column.
A report with a
&lexicalin its select list needs the form's parse sequence and an explicit hidden value in the E2E. Gate it withxxen_report.initialize_run_param_vals_tmp(id)→initialize_parameter_values(id,'Y')→parse_report_sql_columns(id)(a bare parse strips the unresolved anchor and reportsError parsing SQL.), run the lexical's own select standalone and count its lines against the source, and pass the hidden parameter in the scripted run (xxen_api.create_run_id_applies no defaults; without it the request fails withORA-00936).Heading gate: after import, render every parsed column through
xxen_report.column_translation(xrsc.column_name)in the parse session and compare with the headings the original printed, column by column; any difference other than an upper-case identifier heading (rule "Heading limits") is a defect.Open every custom list the way the run form does. For each
lov_queryof the category runselect x.value, x.description from (<query>) x where rownum<3on the instance (substitute the:$flex$binds); a list that only returnsvaluepasses acount(*)gate and fails on every run screen withORA-00904: "X"."DESCRIPTION"(rule "Mechanical rules for generating the parameter XML").Open every run screen the way the form and the add-in do. Per report print
initialize_parameter_values' message on its own line (a parse gate that reuses the variable hides a default the engine rejects), then callxxen_webservices.report_metadataandreport_param_lovfor the independent lists with the metadata defaults as binds: a literal default on a Number parameter is a form error and a failed add-in run (rule, same section).
UAT rounds — answer from the source, never patch the symptom
- A difference the customer reports goes back to that field's inventory row. Re-read the original's code for it, find why the Blitz expression differs, and fix the expression to the original's logic. A fix that makes the reported cell right without citing the source line is a guess, and a wrong guess creates the next round. Never choose a date basis, a rate or a set operator by analogy with another report.
- Check the same pattern on the whole report, then on the category. A defect class found in one field (per-row code not carried, a handler's blank, a literal the loop fills) is searched for in every other field of that report and every other converted report before the round goes out. The field inventories make that a lookup.
- Every round re-runs the value fidelity gate for each report it touches. That means the whole report, not only the changed column, including the full-fetch cost.
- Record what the round taught in the skill. A defect class the gate did not catch becomes a check in
references/value-fidelity.mdor a row type infield_inventory.py, not a note in the engagement folder.
7. Deliverable per report
A short record: classification + evidence, the recommendation or built report (name + category + report_id), the validation result (request_id / row count / equivalence note), and the completed field inventory with the value fidelity results.
The XML deliverable is NOT per report: all reports of the category ship in the single category file (see Output). Import and validate that file as a whole (delete the category's reports on the instance, import the category file, re-run the gates) before handing it over — the per-report intermediates are not what the customer receives.
Output
- ONE report definition XML per category —
reports_Custom_Reports_<ABBR>_with_assignments_vNN.xmlinblitz_converted/next to the gathered folder, holding EVERY report ofCustom Reports <ABBR>(all built/cloned reports of the engagement, not just this batch) plus the<ASSIGNMENTS>of all of them. Never deliver or keep one XML per report: the customer imports and re-imports a single file, and separate files drift apart (decision: product owner). A re-issue is the next_vNNof the same category file. Per-report XMLs are at most an internal build intermediate and are moved to_superseded/once the category file exists.