Back to all posts

JA: Job Acquisition made real

Jul 6, 2026

ja app blog.png

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.