JA: Job Acquisition made real
Jul 6, 2026
JA is the system I built to run my own job search as a real operational workflow: collect job adverts, clean and deduplicate the data, analyse role fit, generate application material, track applications and process responses.
It began as a Power Platform learning project and became a production system I use every day.
Under the hood it now spans Dataverse, model-driven apps, C# plugins and Custom APIs, PCF controls, asynchronous queues, AI processing, SQL analysis and deployment tooling.
This is what it has taken so far:
Learn the platform before fighting it (First Principles): Started by catching up on current Power Platform practice and completed PL-900 and PL-200 => Provisioned a proper environment and built the first model-driven app for adverts, applications, companies, contacts, resumes and profile data.
Use the simplest tool that does the job (KISS): Started with Power Platform dataflows for ingestion and shaping => Hit sequencing, reprocessing and performance problems => Moved the heavier work into C# plugins and direct processing.
Bad inputs do not magically become good outputs (Garbage In, Garbage Out): Incoming adverts were inconsistent and noisy => Added cleaning, normalisation and canonicalisation using heuristics and C# plugins => Production data became consistent enough to analyse and automate against.
The same thing should still be the same thing when it arrives wearing a different hat (Single Source of Truth): The same company, recruiter and advert could arrive from different sources in different shapes => Added canonical entities, source tracking, alternate keys and deduplication => SEEK, LinkedIn and other channels could feed one operational model.
Use the right tool for the question (Law of the Instrument): Dataverse was fine for running the application but awkward for deeper investigation => Built schema generators for .NET 9, .NET Framework 4.6.2 plugins and SQL analysis => Kept Power Platform for production while pushing investigation data into SQL Server.
Do not maintain the same contract by hand in three places (DRY): Maintaining different models across Power Platform, plugins and analysis code became error-prone => Generated early-bound schemas for each target, plus interchangeable data connectors => Schema changes stopped becoming manual copy-and-paste work. The data source or target is selected through dependency injection.
Pick one common language at the boundaries (Canonical Data Model): Job adverts arrived as HTML, the messy kind; prompts wanted clean text; documents needed to be editable and printable => Standardised on Markdown and built C# processors for HTML/PDF/JSON => Markdown, and Markdown => PDF/HTML/JSON.
Fix the interface, not the user (Principle of Least Astonishment): Editing Markdown in standard model-driven app fields was painful => Learned PCF and React and built Markdown editing/viewer controls => Cover letters, resumes and configuration data can be edited full-screen instead of in whatever real estate is left on the form. PCF controls are added as needed to improve the experience.
Make the safe path the easy path (Pit of Success): Command bar actions can become cumbersome in low-code; eventually they favour JavaScript, Custom APIs and plugin registrations wired together manually => Built C# Custom APIs plus registration tooling that emits the JavaScript command actions => UI commands stay tied to the backend implementation.
Queue work that does not belong in the request (Asynchronous Processing): AI processing is slow, costs money and can be unreliable when run synchronously => Built an audited, asynchronous and idempotent request queue => Requests can be scheduled, retried, measured and traced.
Do one thing at a time, properly (Separation of Concerns): One giant AI prompt was trying to do too much => Split processing into advert insights, company insights, pre-engagement, feature extraction, document generation and document validation => Each processor does one job and can evolve independently.
Start with the simplest structure that lets you learn (YAGNI): Early feature extraction started as JSON output => Used the results to learn what information was actually useful => Built proper structured extraction around what survived contact with reality.
Do not trust a confident answer just because it arrived confidently (Trust, but Verify): One-shot document generation produced plausible rubbish => Added a separate validation stage after generation => Documents are checked before use instead of trusted because the model sounded convincing.
Do the cheap work first (Pareto Principle): Scanning up to 5,000 adverts a day made direct AI processing wasteful => Added Markdown cleanup, keyword sets and heuristic scoring before AI => Most adverts are triaged cheaply and only promising ones get deeper analysis.
Do not abuse the systems you depend on (Polite Crawling / Backoff): Large daily scrapes could easily become obnoxious or get blocked => Ran ingestion slowly and on schedule rather than hammering source sites => The pipeline keeps collecting data without pretending rate limits are someone else's problem.
Capability and suitability are not the same thing: A job can match my skills and still be a terrible job for me => Added AI analysis for role fit, likely stress points and company context => The system can say "you could do this" separately from "you should do this".
More context beats a bigger prompt (Context Engineering): Advert + prompt was not enough to produce a useful application => Added company data, role context, my skills, strengths, weaknesses, profiles and resumes => Application material is generated from structured context rather than one giant prompt.
Remove repeated work where it actually happens (DRY): Submission still involved too much copying between JA and browser forms => Built a web-based submission helper => Application context follows me into the manual part of the process.
Use the thing you are building on the problem you actually have (Eat Your Own Dog Food): When I'm not building the system, I'm applying for jobs. Daily. I get to feel the real user pain => I get a much better sense of what is important, what is essential and what is merely nice to have than I would three steps removed from the problem.
Protect your emotional wellbeing: Manually handling rejection emails is emotionally heavy => Built a C# processor using heuristics and lightweight AI extraction to read incoming emails, match them to applications, update state and add notes to the model-driven app timeline => I see fewer rejection emails myself; applications simply get closed without fuss.
Prefer the platform feature before inventing another one (Reuse Before Build): Needed a history of incoming application events => Used Dataverse activity and timeline features rather than inventing another custom history screen => Rejections and recruiter contact appear against the application where they belong.
Old data outlives new ideas (Backward Compatibility): The schema kept changing after real use exposed better structures => Built C# dataflow and back-processing helpers => Existing Power Platform rows can be repaired, migrated and brought forward instead of abandoned.
If you build it, you also get to keep it alive (You Build It, You Run It): Scheduled processors, queues, scrapers and AI jobs became a production operation => Added retries, audit trails, scheduled execution, monitoring and repair paths => JA stopped being a demo long ago and is now resilient, audited, managed and backed up.
Automate the deployment before the deployment automates your weekend (Infrastructure as Code): Solutions, plugins, PCF controls, APIs and JavaScript created too many manual release steps and interdependencies => Built registration, schema and deployment tooling => Changes became repeatable instead of dependent on remembering the magic sequence.
Move fast and break things. Then stop doing that (Fail Fast): Worked too fast, pushed too many buttons and managed to corrupt the Power Platform environment. Microsoft will not help a free account => Built C# schema inspection and replication tools using the Dataverse SDK to investigate and rebuild it => Learned that Power Platform internals can be reverse-engineered, but they really do not enjoy being hacked.
When the platform stops answering the question, build better instruments (Observability): Recovering the broken environment required comparing what existed with what should exist => Extended the schema tooling into inspection and replication work => The recovery problem became reusable platform tooling. The tooling has a working title: RSFP - Trust, but Verify.
Do not preserve a bad decision just because it was your first one (Sunk Cost Fallacy): Some early approaches stopped making sense once the data volume and real workflows arrived => Replaced dataflows, changed schemas, moved workloads between Power Platform, C#, Azure and SQL, and back-processed existing data => The system improved instead of preserving bad first decisions for sentimental reasons.
One source can support more than one use (Single Source of Truth): JA started as advert and application management, but the same profile and content data was useful elsewhere => Added resume and profile management, blog support, AI image automation and the workflow behind LinkedIn content => The system now supports both job acquisition and the public work around it.
Automate the preparation, keep judgement where it matters (Human in the Loop): Content can be researched, prepared and imaged automatically, but LinkedIn posting is still manual => Automated the prep and kept the final judgement and publishing step with me.
What started as a portfolio project is now a working system that has forced me to solve real problems in data quality, automation, AI, ALM, user experience and operations.
I am available for consulting, contract and permanent roles.