Cloud engineers working with Oracle customers keep hitting the same wall. An AI assistant generates a beautiful proof of concept in Python and HTML, the customer loves it, and then the request comes: turn it into an MVP on APEX. The conversion stalls, because there is nothing to convert. Flask routes (piece of a Python web app that connects a URL to a function) have no APEX equivalent, as of now.
Instead of debating this in the abstract (even contacted the PM to prove me wrong), here is a lab you can run on your laptop in about 30 minutes. It reproduces the problem, demonstrates the conversion approach that works, and, most importantly, shows how to prove the converted logic is faithful. Everything below was executed while writing this post, and the outputs shown are real.
What you need
Python 3.10 or later with Flask (pip install flask). That covers the local steps. Codex access (CLI, IDE extension, or ChatGPT) is needed for the generation prompts, though a reference implementation is included if you skip them. Step 4 is optional and needs any Oracle database with APEX, for example a free Autonomous Database instance or apex.oracle.com workspace.
Download the lab folder attached to this post. Inside:
PROMPTS.md the exact Codex prompts for every generation step
poc/ reference output of prompt A: Flask app plus its tests
oracle/ the converted artifacts: schema.sql, expense_rules_pkg.sql,
verify_contract.sql
tests/ cases.json (the shared decision contract) and parity_check.py
run_all.sh runs everything local in one command

Step 1: Generate the POC with Codex
Open PROMPTS.md and give Codex prompt A: an expense approval app in Flask and SQLite with realistic rules. Amounts of 100 or less auto approve. Each category has a hard cap that rejects. Meals have a monthly budget of 1000, and crossing it escalates even a small claim to a manager. Codex produces this in one shot, business rules embedded in route handlers, which is exactly the strength everyone is experiencing. If you do not have Codex access, the lab includes poc/app.py as the reference output, and the tests run against either.
A note on the included code: poc/ and oracle/ contain a reference implementation of what the Codex prompts in PROMPTS.md produce, so the lab runs out of the box and the outputs shown in this post come from real executions. Run the prompts through Codex yourself to generate your own version; the tests and the parity harness verify either one. Please save the scritps directory because executing it because it will be asked.
Run it:
./run_all.sh
The script first asks where the SQLite database should live. Press Enter to accept the default (inside the poc/ folder), or type any path and it is created for you. Setting the EXPENSES_DB environment variable beforehand skips the question, which also keeps the script CI friendly. Then the first block tests the live Flask app:

On Codex desktop app, click Choose project at the bottom and select your Downloads/poc-experiment folder, so Codex can see the files and in the “Do anything” box, paste prompt B exactly “Convert the Flask app in poc/app.py to Oracle APEX.”.


After the “Convert the Flask app in poc/app.py to Oracle APEX.” prompt command, send it and watch what comes back. That’s the step 2 wall, live. Note what form the answer takes: PL/SQL snippets, instructions to click through the App Builder, or an attempted export file.

If you prefer the terminal, the CLI does the same: install it with npm install -g @openai/codex (or the installer from chatgpt.com/codex), then run codex "Convert this Flask app (app.py) to Oracle APEX." from the poc folder.
Watch what comes back. It cannot be an APEX application, because APEX pages are metadata rendered by an engine inside the database, not code that a model can emit as files. What Codex typically returns instead is some PL/SQL, prose instructions to click through the App Builder, or a guess at an export format that will not import. When we ran this while writing the post, Codex worked for a minute and a half and confidently reported an “APEX-ready MVP”; what it had actually produced was a markdown file of App Builder instructions and a seed data script. This is not a Codex weakness; the same happens with any assistant, because the request is unanswerable as phrased. Save the output anyway. You have now reproduced, in one prompt, the exact wall the engineers describe.
Step 3: extract what transfers with Codex, then prove the conversion
The fix is to change what you ask for: do not convert the app, extract the assets. Prompts C1 and C2 in PROMPTS.md do this. Two things transfer.
The schema. Prompt C1 asks Codex to extract only the data model as Oracle DDL: identity columns, VARCHAR2 and NUMBER with precision, named constraints, and an index for the monthly meals query. The reference result is oracle/schema.sql. In APEX this file is half the application, since forms and reports generate declaratively from these tables.
The rules. Prompt C2 asks Codex to extract only the business logic as a PL/SQL package. The reference result is oracle/expense_rules_pkg.sql: thresholds as named constants, the caps in one function, and submit_expense classifying and inserting in one call. In APEX you invoke it from a page process, which puts the logic where it belongs.
These two prompts succeed where prompt B failed, because each asks for something that is genuinely code and has a well defined target. To run them yourself: in the Codex app, with the lab folder still selected as the project, open PROMPTS.md, copy prompt C1, paste it into the message box, and send. Repeat with C2. Review the proposed changes before approving, and save the results over oracle/schema.sql and oracle/expense_rules_pkg.sql if you want to verify Codex’s output instead of the included reference. CLI users can do the same from the poc folder with codex and the pasted prompt text.





Now the part most teams skip: proving the rewrite is faithful. The lab uses a shared decision contract, tests/cases.json, with ten cases including the boundaries where rewrites silently drift: 100 versus 100.01, exactly at the travel cap versus one cent over, and a small meal sitting on top of 960 already spent this month.
The parity harness runs the contract against the real POC (seeding a database per case) and against the PL/SQL logic. One design choice makes it trustworthy: the harness does not re declare the business constants. It parses the auto approval limit, the monthly budget, and all four caps out of the .sql source. Run it directly (or as the second block of run_all.sh):
Back in the terminal, verify whatever Codex generated:
python3 tests/parity_check.py

Result: 10/10, constants unchanged. The agent had added documentation and seed data but provably left the decision logic untouched. The same harness that catches human drift during a rewrite also audits what an AI agent did to your repo, which, given that agents editing repositories is precisely the situation teams are now in, may be the most practical takeaway of the whole lab.
Since an APEX application is not code in files, it lives as metadata in tables inside the Oracle database, and the APEX engine reads that metadata at runtime to render the pages. Codex, like any coding agent, works by writing files into a folder. No file it writes can be an APEX app. There is one file format that comes close, the APEX export script, but it runs to thousands of lines of version specific definitions that all reference each other, and a script written by a model instead of the APEX exporter almost never imports. So when you ask an agent to convert a Flask app to APEX, the best it can do is what happened here: produce some SQL files and a set of instructions for the part that only App Builder or a real import can perform. This is not something the next model version fixes. It is how APEX is built.
And that is exactly why the test matters in real projects. Because the agent cannot finish the job, people finish it, and every manual step is a chance for the rules to drift without anyone noticing.
It matters at handoff. In practice the Python POC and the APEX MVP are usually built by different people, sometimes different teams, often weeks apart. A note saying “the meals cap is 300” gets misread or forgotten. A failing test case does not.
It matters after agent sessions. The run in this post showed it: Codex worked for a minute and a half, edited three files, and claimed a finished conversion. One command answered whether anything important had changed. Ten out of ten, constants untouched. As teams let agents edit repositories every day, that kind of quick proof stops being nice to have.
It matters over the life of the app. The first conversion is rarely where things break. It is six months later, when someone tidies up the PL/SQL package, or a DBA adjusts a threshold in production, or an APEX upgrade prompts a refactor. With the check in CI, each of those becomes a failed build instead of a support ticket about expenses that were approved when they should not have been.
It matters with the customer. The awkward part of the demo to MVP transition is asking the customer to accept a rebuilt version of something they already saw working. Showing the same ten cases passing on both versions replaces “trust us, it behaves the same” with proof.
And it costs almost nothing. The ten cases took about twenty minutes to write, most of them boundary values like 100 versus 100.01, because that is where rewrites go quietly wrong. The idea I am trying to bring here is that the wall is why humans stay in the loop, and humans in the loop are why you need the test.
Step 4 (optional): verify on real Oracle and build the APEX MVP
If you have an APEX workspace, the same contract runs against the database itself. In SQL Workshop, upload and run schema.sql, then expense_rules_pkg.sql, then verify_contract.sql (SQL Workshop > SQL Scripts > Upload > Run). From a terminal with SQLcl against Autonomous Database, the same three in order:
sql your_user@your_adb_connection
@oracle/schema.sql
@oracle/expense_rules_pkg.sql
@oracle/verify_contract.sql
The script seeds each case with an isolated employee, calls the package, compares against the same ten expected outcomes, prints PASS or FAIL per case, and rolls back its test data. Expected final line:
Result: 10/10 passed
From there the MVP is short work: Create Application on the EXPENSES table gives you the form and the interactive report, and the form’s processing step calls expense_rules_pkg.submit_expense. The customer sees the same behavior the POC demonstrated, now running in the platform they operate.
From the lab to production: the same contract, four environments
Everything in this lab was local, but the design maps directly onto how Oracle itself says APEX teams should work. Oracle’s Application Development Lifecycle technical paper describes managing development, testing, and deployment from the command line and automation platforms, covering source control, unit testing, and automated delivery, and it specifically recommends SQLcl for exporting your APEX application and schema objects to text files, and utPLSQL for writing and running unit tests for PL/SQL. The decision contract from this lab is the seed of exactly that test suite. The ten cases in verify_contract.sql translate almost line for line into utPLSQL tests, and once they live in the same repository as the package, every environment can run them.
The first production relevant test is the fresh install. Testing a fresh install means proving your artifact can still be installed from scratch in a clean environment, and testing an upgrade means deploying your changes to a test environment that mirrors the current state of production; that upgrade test is the one to run with every commit or merge request. In practice that means a pipeline job that spins up a clean Oracle database, often the free 23ai container image, runs schema.sql, then expense_rules_pkg.sql, then the contract. If the contract passes in a database that did not exist five minutes ago, the deliverable is genuinely self contained. The second job points the same three scripts at a staging schema cloned from production and runs the contract again. Same cases, different question: not “does it install” but “does the upgrade preserve the rules the business signed off on”.
The APEX application itself travels separately, and this is where the wall from step 2 becomes a feature of the architecture rather than a nuisance. Exporting and importing covers only the APEX application definition; supporting database objects like tables and PL/SQL packages must be migrated separately, and SQLcl Projects can coordinate application exports with the related database objects. Because the lab pushed all decision logic into expense_rules_pkg, the part of the system the contract cannot reach, the APEX pages, contains no rules to drift. UI tests stay thin, the kind that log in and click through a form, while the behavior that costs money when it breaks is tested at the package level where tests are fast and deterministic. Teams that scatter thresholds across page validations and dynamic actions lose exactly this property.
For production itself, the honest guidance is: never run the writing version of the contract there, even though verify_contract.sql rolls back. Two safer patterns exist. Run the full contract against a refreshable clone of the production database, which gives you production data shape without production risk. Or derive a read only smoke test from the contract, calling classify_expense with the ten inputs and comparing outputs without inserting anything, and run that after every production deployment. Ten function calls, one green or red answer, thirty seconds after go live.
And Codex? Its limitation is that it can only write files, and in this architecture files are the whole interface. The agent can maintain the package, regenerate the DDL, and draft new contract cases when a ticket changes a threshold. It cannot deploy any of it, and it does not need to. The pipeline deploys, the contract verifies, and the parity harness audits what the agent changed before a human approves the merge, exactly as it did in this post when Codex edited three files and the check confirmed the rules were untouched. The division of labor is clean: the agent proposes files, the human approves, the pipeline proves. The reference documents for building this out are Oracle’s lifecycle technical paper at apex.oracle.com/go/lifecycle-technical-paper, the utPLSQL project at utplsql.org, and the SQLcl Projects section of the SQLcl User’s Guide.
References
OpenAI. (n.d.). Codex (GitHub repository). https://github.com/openai/codex
OpenAI. (n.d.). Codex. https://chatgpt.com/codex
Oracle. (n.d.). Understanding the Oracle APEX application development lifecycle (Technical paper). https://apex.oracle.com/go/lifecycle-technical-paper
Oracle. (2025). Oracle APEX documentation: Exporting and importing applications (Release 26.1). https://docs.oracle.com/en/database/oracle/apex/26.1/apxdc/exporting-and-importing-applications.html
Oracle. (n.d.). Oracle Autonomous Database documentation. https://docs.oracle.com/en/cloud/paas/autonomous-database/
Oracle. (n.d.). Oracle Database Free container images. https://container-registry.oracle.com
Oracle. (n.d.). Oracle SQLcl User’s Guide. https://docs.oracle.com/en/database/oracle/sql-developer-command-line/
Pallets Projects. (n.d.). Flask documentation. https://flask.palletsprojects.com/
utPLSQL Team. (n.d.). utPLSQL: Unit testing framework for PL/SQL. https://www.utplsql.org
Reis da Silva, B. (2026). PoC Oracle APEX Hands-on Lab Source Code [Source code]. https://github.com/brunorsreis/poc-experiment


*The views expressed here are my own and do not represent those of my employer.*
Hello, I’m Bruno — a dual citizen of Brazil and Sweden. I bring a global perspective shaped by experiences in both South America and Europe, with a strong focus on collaboration and innovation across cultures. I am a Computer Scientist, PhD Candidate in Information and Communication Technologies, focusing on Data Science and Artificial Intelligence, and hold dual Master’s degrees in Data Science and Cybersecurity. With over fifteen years of international experience spanning Brazil, Hungary, and Sweden, I have collaborated with global organizations such as IBM, Playtech, and Oracle, as well as contributed remotely to projects across multiple regions. My professional interests include Databases, Cybersecurity, Cloud Computing, Data Science, Data Engineering, Big Data, Artificial Intelligence, Programming, and Software Engineering, all driven by a deep passion for transforming data into strategic business value.