<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Anthony Leignel</title>
    <description>The latest articles on DEV Community by Anthony Leignel (@palks_studio).</description>
    <link>https://dev.to/palks_studio</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3772766%2F42db137c-2300-4d6b-80ff-8d0acf68f56a.jpg</url>
      <title>DEV Community: Anthony Leignel</title>
      <link>https://dev.to/palks_studio</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/palks_studio"/>
    <language>en</language>
    <item>
      <title>What Happens When Electronic Invoicing, Digital Identity and Payments Become Interconnected?</title>
      <dc:creator>Anthony Leignel</dc:creator>
      <pubDate>Fri, 04 Sep 2026 15:12:27 +0000</pubDate>
      <link>https://dev.to/palks_studio/what-happens-when-electronic-invoicing-digital-identity-and-payments-become-interconnected-h5j</link>
      <guid>https://dev.to/palks_studio/what-happens-when-electronic-invoicing-digital-identity-and-payments-become-interconnected-h5j</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0pkn0zum7jgkv62ib8jx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0pkn0zum7jgkv62ib8jx.png" alt="Illustration connecting electronic invoicing, transaction data, digital identity, environmental data and payment infrastructures" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Electronic invoicing is usually presented as an accounting and tax reform.&lt;/p&gt;

&lt;p&gt;Structured invoices.&lt;/p&gt;

&lt;p&gt;Automated reporting.&lt;/p&gt;

&lt;p&gt;Standardized data.&lt;/p&gt;

&lt;p&gt;New obligations for businesses.&lt;/p&gt;

&lt;p&gt;But while working on electronic invoicing systems, I started looking beyond the invoice itself.&lt;/p&gt;

&lt;p&gt;What happens to the data once transactions become structured?&lt;/p&gt;

&lt;p&gt;What other European digital infrastructures can connect to it?&lt;/p&gt;

&lt;p&gt;And how far do those connections actually go?&lt;/p&gt;

&lt;p&gt;That question led to a much larger investigation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Electronic Invoicing Is Only One Piece
&lt;/h2&gt;

&lt;p&gt;Electronic invoicing does not exist in isolation.&lt;/p&gt;

&lt;p&gt;At the same time, Europe is developing or deploying other digital infrastructures around products, identity and payments.&lt;/p&gt;

&lt;p&gt;Taken separately, they look like different projects.&lt;/p&gt;

&lt;p&gt;But their interfaces matter.&lt;/p&gt;

&lt;p&gt;A transaction can identify a product.&lt;/p&gt;

&lt;p&gt;A product can be connected to a Digital Product Passport.&lt;/p&gt;

&lt;p&gt;A Digital Product Passport can contain structured information about that product, including environmental information.&lt;/p&gt;

&lt;p&gt;Digital wallets can carry electronic credentials.&lt;/p&gt;

&lt;p&gt;Payment infrastructures can interact with external systems and conditions.&lt;/p&gt;

&lt;p&gt;The interesting question is therefore no longer whether each system exists independently.&lt;/p&gt;

&lt;p&gt;It is whether they can communicate.&lt;/p&gt;




&lt;h2&gt;
  
  
  Following the Data Changes the Picture
&lt;/h2&gt;

&lt;p&gt;The first chain examined in the investigation looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Electronic invoicing
   ↓
Transaction data
   ↓
Product identification
   ↓
Digital Product Passport
   ↓
Environmental data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;None of these steps, considered individually, demonstrates the existence of a larger unified system.&lt;/p&gt;

&lt;p&gt;But the connections themselves are important.&lt;/p&gt;

&lt;p&gt;They show how structured transaction data can progressively be associated with increasingly detailed information about what was purchased.&lt;/p&gt;




&lt;h2&gt;
  
  
  Identity and Payments Form Another Chain
&lt;/h2&gt;

&lt;p&gt;A second group of infrastructures develops around identity and payments.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Digital identity / wallet
   ↓
Electronic credential
   ↓
Payment
   ↓
External conditions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where the distinction between technical capability and actual use becomes essential.&lt;/p&gt;

&lt;p&gt;The digital euro, for example, must not simply be described as "programmable money."&lt;/p&gt;

&lt;p&gt;The European framework explicitly distinguishes programmable money from conditional payments.&lt;/p&gt;

&lt;p&gt;A conditional payment can depend on predefined conditions without turning the currency itself into programmable money.&lt;/p&gt;

&lt;p&gt;That distinction matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bridges Are No Longer Entirely Theoretical
&lt;/h2&gt;

&lt;p&gt;This was one of the most interesting results of the investigation.&lt;/p&gt;

&lt;p&gt;I started with a question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Could these systems eventually be connected?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After examining official documentation, standards, technical architectures and experiments, the question changed.&lt;/p&gt;

&lt;p&gt;Several bridges already exist, are standardized, planned or have been experimented with.&lt;/p&gt;

&lt;p&gt;So the investigation moved from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;**HYPOTHESIS**

These systems might
one day be connected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;**OBSERVATION**

Several connections already exist,
are standardized,
planned
or have been experimented with
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That does not mean everything is already connected.&lt;/p&gt;

&lt;p&gt;It means the documentary boundary is further down the chain than it initially appears.&lt;/p&gt;




&lt;h2&gt;
  
  
  And This Is Where the Evidence Stops
&lt;/h2&gt;

&lt;p&gt;There is a final chain that the investigation does not establish.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Environmental data
   ↓
Individual profile
   ↓
Imposed rule
   ↓
Economic or monetary restriction
   ↓
NOT ESTABLISHED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This distinction is fundamental.&lt;/p&gt;

&lt;p&gt;Technical possibility is not the same thing as actual use.&lt;/p&gt;

&lt;p&gt;Actual use is not the same thing as legal authorization.&lt;/p&gt;

&lt;p&gt;And interoperability between infrastructures does not prove that they were designed for one common purpose.&lt;/p&gt;

&lt;p&gt;The investigation therefore does not claim that an environmental score is currently being used to restrict an individual's payments.&lt;/p&gt;

&lt;p&gt;It does not establish the existence of a centralized system controlling individual consumption.&lt;/p&gt;

&lt;p&gt;And it does not attribute an undocumented intention to the institutions involved.&lt;/p&gt;




&lt;h2&gt;
  
  
  But the Opposite Conclusion Would Also Be Wrong
&lt;/h2&gt;

&lt;p&gt;Saying that such a unified system exists today would go beyond the evidence.&lt;/p&gt;

&lt;p&gt;But saying that the different infrastructures are completely isolated would also ignore documented technical connections.&lt;/p&gt;

&lt;p&gt;The reality is more interesting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Separate projects
   ↓
Structured data
   ↓
Standardized interfaces
   ↓
Increasing interoperability
   ↓
?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Electronic invoicing, product identification, environmental information, digital identity and payment systems remain separate projects.&lt;/p&gt;

&lt;p&gt;But several of the technical bridges between them are real.&lt;/p&gt;

&lt;p&gt;And as interoperability increases, studying each infrastructure independently becomes less useful.&lt;/p&gt;

&lt;p&gt;The architecture has to be examined as a whole.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I Published the Investigation
&lt;/h2&gt;

&lt;p&gt;I wanted the distinction between facts and deductions to remain visible.&lt;/p&gt;

&lt;p&gt;So every major element of the investigation is classified according to its level of evidence.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ESTABLISHED

TECHNICALLY DEDUCIBLE

HYPOTHESIS

TO BE ESTABLISHED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The objective is not to prove a predetermined conclusion.&lt;/p&gt;

&lt;p&gt;It is to determine exactly where the documentation takes us.&lt;/p&gt;

&lt;p&gt;And where it stops.&lt;/p&gt;

&lt;p&gt;The complete investigation is public, including the sources, individual chapters and methodology.&lt;/p&gt;




&lt;h2&gt;
  
  
  Read the Investigation
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://palks-studio.com/assets/pdf/electronic-invoicing-investigation.pdf" rel="noopener noreferrer"&gt;English version&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://palks-studio.com/assets/pdf/facturation-electronique-enquete.pdf" rel="noopener noreferrer"&gt;Version française&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/Palks-Studio/facturation-electronique-enquete" rel="noopener noreferrer"&gt;GitHub repository — complete investigation and sources&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The GitHub repository contains the complete investigation, the seven individual chapters and the source register.&lt;/p&gt;

&lt;p&gt;Everything is public so the connections can be verified, challenged or explored further.&lt;/p&gt;




&lt;h2&gt;
  
  
  One Question Remains
&lt;/h2&gt;

&lt;p&gt;Electronic invoicing is arriving now.&lt;/p&gt;

&lt;p&gt;Digital Product Passports are developing.&lt;/p&gt;

&lt;p&gt;European digital identity infrastructure is developing.&lt;/p&gt;

&lt;p&gt;Payment infrastructure is evolving too.&lt;/p&gt;

&lt;p&gt;Each project has its own purpose, legal framework and timeline.&lt;/p&gt;

&lt;p&gt;But their ability to exchange structured data is increasing.&lt;/p&gt;

&lt;p&gt;So perhaps the most interesting question is no longer:&lt;/p&gt;

&lt;p&gt;What does electronic invoicing change?&lt;/p&gt;

&lt;p&gt;It is:&lt;/p&gt;

&lt;p&gt;What becomes technically possible when systems capable of identifying a transaction, a product, its characteristics, an identity and a payment become interoperable?&lt;/p&gt;

&lt;p&gt;That is the question this investigation tries to document.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://palks-studio.com" rel="noopener noreferrer"&gt;https://palks-studio.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>einvoicing</category>
      <category>privacy</category>
      <category>data</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Turning Google Search Console Exports Into Actionable SEO Data</title>
      <dc:creator>Anthony Leignel</dc:creator>
      <pubDate>Wed, 02 Sep 2026 00:44:12 +0000</pubDate>
      <link>https://dev.to/palks_studio/turning-google-search-console-exports-into-actionable-seo-data-44</link>
      <guid>https://dev.to/palks_studio/turning-google-search-console-exports-into-actionable-seo-data-44</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhiopo7uzxcgh6u4i81tr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhiopo7uzxcgh6u4i81tr.png" alt="SEO Lens interface showing the Google Search Console data analyzed by the tool" width="800" height="759"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Google Search Console provides the data. The challenge is knowing where to look.
&lt;/h2&gt;

&lt;p&gt;Google Search Console already provides a significant amount of information about how a website performs in search.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clicks&lt;/li&gt;
&lt;li&gt;Impressions&lt;/li&gt;
&lt;li&gt;CTR&lt;/li&gt;
&lt;li&gt;Average positions&lt;/li&gt;
&lt;li&gt;Queries&lt;/li&gt;
&lt;li&gt;Pages&lt;/li&gt;
&lt;li&gt;Countries&lt;/li&gt;
&lt;li&gt;Devices&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The data is there.&lt;/p&gt;

&lt;p&gt;But exporting it often produces several CSV files containing hundreds or thousands of rows.&lt;/p&gt;

&lt;p&gt;At that point, the problem is no longer accessing the data.&lt;/p&gt;

&lt;p&gt;The problem is turning it into something that can be examined quickly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Raw data is useful, but structure matters
&lt;/h2&gt;

&lt;p&gt;A Search Console export can answer many questions.&lt;/p&gt;

&lt;p&gt;Which queries generate impressions but few clicks?&lt;/p&gt;

&lt;p&gt;Which keywords are already close to the first positions?&lt;/p&gt;

&lt;p&gt;Which pages receive visibility?&lt;/p&gt;

&lt;p&gt;How has performance changed during the selected period?&lt;/p&gt;

&lt;p&gt;Which countries and devices account for the traffic?&lt;/p&gt;

&lt;p&gt;All of this information exists in the export.&lt;/p&gt;

&lt;p&gt;But examining separate CSV files manually makes relationships between those metrics harder to see.&lt;/p&gt;

&lt;p&gt;A useful analysis layer does not need to invent new data.&lt;/p&gt;

&lt;p&gt;It needs to organize the existing data in a way that makes patterns and potential priorities easier to identify.&lt;/p&gt;




&lt;h2&gt;
  
  
  Building an analysis layer instead of another SEO score
&lt;/h2&gt;

&lt;p&gt;This was the idea behind SEO Lens.&lt;/p&gt;

&lt;p&gt;SEO Lens is a small browser-based tool I built to transform a Google Search Console ZIP export into a structured visual report.&lt;/p&gt;

&lt;p&gt;The objective was deliberately limited.&lt;/p&gt;

&lt;p&gt;It does not attempt to assign an arbitrary SEO score such as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SEO score: 87/100&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A single number would hide most of the information that actually deserves investigation.&lt;/p&gt;

&lt;p&gt;Instead, the report keeps the underlying Search Console metrics visible and organizes them into several analytical views:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;performance overview&lt;/li&gt;
&lt;li&gt;performance evolution&lt;/li&gt;
&lt;li&gt;ranking distribution&lt;/li&gt;
&lt;li&gt;opportunities&lt;/li&gt;
&lt;li&gt;page performance&lt;/li&gt;
&lt;li&gt;countries and devices&lt;/li&gt;
&lt;li&gt;diagnostic&lt;/li&gt;
&lt;li&gt;SEO priorities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is not to decide whether a website is "good" or "bad".&lt;/p&gt;

&lt;p&gt;The goal is to make the available data easier to inspect.&lt;/p&gt;




&lt;h2&gt;
  
  
  Keeping the architecture simple
&lt;/h2&gt;

&lt;p&gt;The processing pipeline is intentionally straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IMPORT
  ↓
PARSER
  ↓
NORMALIZATION
  ↓
ANALYSIS
  ↓
INSIGHTS / PRIORITIZATION
  ↓
RENDERING
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user exports data from Google Search Console as a ZIP file.&lt;/p&gt;

&lt;p&gt;The ZIP is opened directly in the browser.&lt;/p&gt;

&lt;p&gt;The CSV files are identified, parsed and normalized before the different analyses are calculated.&lt;/p&gt;

&lt;p&gt;The resulting report is then rendered in the same page.&lt;/p&gt;

&lt;p&gt;There is no backend involved in the analysis.&lt;/p&gt;




&lt;h2&gt;
  
  
  Handling different Search Console exports
&lt;/h2&gt;

&lt;p&gt;One interesting implementation detail is that Search Console exports are not necessarily identical depending on language.&lt;/p&gt;

&lt;p&gt;For example, a French export can contain files such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Graphique.csv
Requêtes.csv
Pages.csv
Pays.csv
Appareils.csv
Filtres.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The analyzer therefore needs a normalization layer.&lt;/p&gt;

&lt;p&gt;Instead of making the analysis logic depend directly on a specific filename or language, aliases are used to identify equivalent datasets.&lt;/p&gt;

&lt;p&gt;Once the files and columns have been normalized, the rest of the analysis can operate on a consistent internal structure.&lt;/p&gt;

&lt;p&gt;This keeps localization concerns separate from analytical logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Finding opportunities without inventing information
&lt;/h2&gt;

&lt;p&gt;The analysis is deterministic.&lt;/p&gt;

&lt;p&gt;SEO Lens does not use AI to interpret the export.&lt;/p&gt;

&lt;p&gt;For example, the tool can identify situations such as queries receiving significant impressions but relatively few clicks, or queries already ranking within ranges where further investigation may be useful.&lt;/p&gt;

&lt;p&gt;The important distinction is that these are signals derived from the provided Search Console data.&lt;/p&gt;

&lt;p&gt;They are not claims about the entire website.&lt;/p&gt;

&lt;p&gt;A query with a low CTR may deserve investigation, but the export alone cannot explain why its CTR is low.&lt;/p&gt;

&lt;p&gt;A page losing clicks may deserve attention, but Search Console data alone cannot diagnose every possible technical or content-related cause.&lt;/p&gt;

&lt;p&gt;The tool can identify where to look.&lt;/p&gt;

&lt;p&gt;It should not pretend to know what the data cannot establish.&lt;/p&gt;




&lt;h2&gt;
  
  
  Privacy as an architectural property
&lt;/h2&gt;

&lt;p&gt;Another requirement was that importing Search Console data should not require sending it somewhere else.&lt;/p&gt;

&lt;p&gt;SEO Lens therefore performs the analysis locally in the browser.&lt;/p&gt;

&lt;p&gt;There is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no account&lt;/li&gt;
&lt;li&gt;no Google API connection&lt;/li&gt;
&lt;li&gt;no remote database&lt;/li&gt;
&lt;li&gt;no AI service&lt;/li&gt;
&lt;li&gt;no upload of the Search Console export to Palks Studio&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The selected ZIP remains on the user's device while it is being processed.&lt;/p&gt;

&lt;p&gt;This is not an additional privacy layer added around the application.&lt;/p&gt;

&lt;p&gt;It is a consequence of the architecture itself.&lt;/p&gt;

&lt;p&gt;If the server never needs the data, there is no reason to transmit it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why no direct Google Search Console API?
&lt;/h2&gt;

&lt;p&gt;Connecting directly to the Search Console API could make importing data more convenient.&lt;/p&gt;

&lt;p&gt;But it would also change the nature of the project.&lt;/p&gt;

&lt;p&gt;OAuth, permissions, tokens, API configuration and server-side infrastructure would introduce additional dependencies and additional responsibilities.&lt;/p&gt;

&lt;p&gt;For this use case, the existing Search Console export already contains the necessary input.&lt;/p&gt;

&lt;p&gt;The workflow remains simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Google Search Console
        ↓
     ZIP export
        ↓
     SEO Lens
        ↓
   Visual report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It requires one manual step, but removes several architectural ones.&lt;/p&gt;

&lt;p&gt;That trade-off is intentional.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the tool does not do
&lt;/h2&gt;

&lt;p&gt;Defining the limits of an analysis tool is as important as defining its features.&lt;/p&gt;

&lt;p&gt;SEO Lens does not perform:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;website crawling&lt;/li&gt;
&lt;li&gt;complete technical SEO audits&lt;/li&gt;
&lt;li&gt;backlink analysis&lt;/li&gt;
&lt;li&gt;HTML content analysis&lt;/li&gt;
&lt;li&gt;website modifications&lt;/li&gt;
&lt;li&gt;direct Search Console synchronization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It only analyzes the information contained in the Search Console export provided by the user.&lt;/p&gt;

&lt;p&gt;The resulting report should therefore be considered a structured reading of Search Console data, not a complete SEO audit.&lt;/p&gt;




&lt;h2&gt;
  
  
  Small tools can solve narrow problems well
&lt;/h2&gt;

&lt;p&gt;Not every technical problem requires a platform, an account system or a large application architecture.&lt;/p&gt;

&lt;p&gt;Sometimes the useful part is simply the transformation between an existing input and a more understandable output.&lt;/p&gt;

&lt;p&gt;In this case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;raw Search Console data
        ↓
structured information
        ↓
points worth investigating
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The implementation can remain relatively small because the scope remains explicit.&lt;/p&gt;

&lt;p&gt;There is no attempt to replace Search Console.&lt;/p&gt;

&lt;p&gt;There is no attempt to automate every SEO decision.&lt;/p&gt;

&lt;p&gt;The tool sits between the export and the person analyzing it.&lt;/p&gt;




&lt;p&gt;SEO Lens&lt;/p&gt;

&lt;p&gt;SEO Lens is available for free and runs directly in the browser.&lt;/p&gt;

&lt;p&gt;No account is required and the Search Console export is processed locally.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://palks-studio.com/en/seo-lens" rel="noopener noreferrer"&gt;Try SEO Lens&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The project also has a GitHub repository containing its technical presentation and documentation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Palks-Studio/seo-lens" rel="noopener noreferrer"&gt;SEO Lens on GitHub&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://palks-studio.com" rel="noopener noreferrer"&gt;https://palks-studio.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>seo</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>privacy</category>
    </item>
    <item>
      <title>How Many Obsolete Dependencies Are You Using Without Knowing It? An Open Source Checker</title>
      <dc:creator>Anthony Leignel</dc:creator>
      <pubDate>Sun, 16 Aug 2026 15:50:57 +0000</pubDate>
      <link>https://dev.to/palks_studio/how-many-obsolete-dependencies-are-you-using-without-knowing-it-an-open-source-checker-4nnl</link>
      <guid>https://dev.to/palks_studio/how-many-obsolete-dependencies-are-you-using-without-knowing-it-an-open-source-checker-4nnl</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0cyneqyxe853med87awr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0cyneqyxe853med87awr.png" alt="Dependency Security Checker open source project" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Security also starts with what we install
&lt;/h2&gt;

&lt;p&gt;We install Python packages, VS Code extensions, tools that help us save time, test an idea, or simply improve our development environment.&lt;/p&gt;

&lt;p&gt;Then we move on.&lt;/p&gt;

&lt;p&gt;A few months or years later, some of those projects have been deprecated, archived, renamed, or are simply no longer maintained.&lt;/p&gt;

&lt;p&gt;And they are still installed.&lt;/p&gt;

&lt;p&gt;That is exactly the problem I wanted to address with &lt;strong&gt;Dependency Security Checker&lt;/strong&gt;, an open source tool that locally scans installed Python packages and VS Code extensions, then compares them against documented alert databases.&lt;/p&gt;

&lt;p&gt;The project is open source and available here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Palks-Studio/dependency-security-checker" rel="noopener noreferrer"&gt;Dependency Security Checker, GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  An alert before a vulnerability becomes your problem
&lt;/h3&gt;

&lt;p&gt;The goal is not to claim that a dependency is vulnerable simply because it is no longer maintained.&lt;/p&gt;

&lt;p&gt;That would be incorrect.&lt;/p&gt;

&lt;p&gt;The goal is much simpler: &lt;strong&gt;to warn you.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If an installed package has officially been deprecated, you should be able to know about it.&lt;/p&gt;

&lt;p&gt;If a VS Code extension you still use has been archived, you should be able to know that too.&lt;/p&gt;

&lt;p&gt;And if a project has been renamed or its maintainers now recommend an alternative, that information should be easy to find.&lt;/p&gt;

&lt;h3&gt;
  
  
  A tool that does not make decisions for you
&lt;/h3&gt;

&lt;p&gt;Dependency Security Checker does not uninstall anything, disable anything, update anything, or modify your projects.&lt;/p&gt;

&lt;p&gt;It scans, detects, and displays the available information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[!] package-name 1.0.0
    Status / Statut: DEPRECATED / DÉPRÉCIÉ

    Reason:
    ...

    Raison :
    ...

    Suggested replacement / Remplacement suggéré: ...

    Source: ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there, the decision is yours.&lt;/p&gt;

&lt;p&gt;You can check the source, understand why the project was flagged, review how you use it, and decide whether it should be kept, replaced, updated, or removed.&lt;/p&gt;

&lt;p&gt;This approach matters because a security-related tool should not create another problem by automatically modifying an environment it does not fully understand.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deprecated does not mean vulnerable
&lt;/h3&gt;

&lt;p&gt;This is probably the most important distinction behind the project.&lt;/p&gt;

&lt;p&gt;An abandoned dependency is not automatically vulnerable.&lt;/p&gt;

&lt;p&gt;Likewise, the absence of an alert does not mean a dependency is secure or free from vulnerabilities.&lt;/p&gt;

&lt;p&gt;But when a project is no longer maintained, one question becomes legitimate:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who will fix the next vulnerability if one is discovered?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is where dependency maintenance becomes directly relevant to security.&lt;/p&gt;

&lt;p&gt;Vulnerabilities do not only come from the code we write ourselves, they can also exist in the different components we add to our environments and projects.&lt;/p&gt;

&lt;p&gt;With the number of packages, libraries, extensions, and development tools a modern environment can accumulate, it becomes increasingly difficult to remember exactly what is still installed and whether every component is still maintained.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why is it open source?
&lt;/h3&gt;

&lt;p&gt;Because when it comes to security, I prefer being able to see what a tool actually does.&lt;/p&gt;

&lt;p&gt;Dependency Security Checker runs locally, its alert databases are visible, its sources can be checked, and its behavior can be inspected directly in the code.&lt;/p&gt;

&lt;p&gt;It does not need to send your list of packages or VS Code extensions to a remote service to perform the scan.&lt;/p&gt;

&lt;p&gt;Today, with software failures, compromised dependencies, abandoned projects, and supply-chain security issues becoming increasingly difficult to ignore, security should not be treated as something we think about only after a problem occurs.&lt;/p&gt;

&lt;p&gt;Making the project open source also means something important:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You do not have to simply trust the tool.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can inspect it.&lt;/p&gt;

&lt;p&gt;You can verify its data.&lt;/p&gt;

&lt;p&gt;You can contribute.&lt;/p&gt;

&lt;p&gt;You can report incorrect information or submit another officially deprecated or abandoned dependency with a public source.&lt;/p&gt;

&lt;h3&gt;
  
  
  You do not need to be a developer to understand the result
&lt;/h3&gt;

&lt;p&gt;I also wanted to avoid building something that only makes sense to someone who spends every day working in a terminal.&lt;/p&gt;

&lt;p&gt;A developer can use it as a quick check of their environment, but a less technical user can also run the checker and understand the essential result.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No known alert found&lt;/strong&gt;, nothing installed matched the current database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An alert appears&lt;/strong&gt;, something deserves your attention.&lt;/p&gt;

&lt;p&gt;Then you get the status, the reason, a suggested replacement when one exists, and most importantly, the source.&lt;/p&gt;

&lt;p&gt;No mysterious score.&lt;/p&gt;

&lt;p&gt;No invented risk level.&lt;/p&gt;

&lt;p&gt;No automatic decision.&lt;/p&gt;

&lt;p&gt;The information remains understandable.&lt;/p&gt;

&lt;h3&gt;
  
  
  A database designed to evolve
&lt;/h3&gt;

&lt;p&gt;The first version already contains hundreds of documented alerts across Python packages and VS Code extensions.&lt;/p&gt;

&lt;p&gt;But the database is not intended to pretend it is exhaustive.&lt;/p&gt;

&lt;p&gt;New projects will be abandoned.&lt;/p&gt;

&lt;p&gt;Others will be archived.&lt;/p&gt;

&lt;p&gt;Some libraries will be renamed or replaced.&lt;/p&gt;

&lt;p&gt;The project can therefore evolve over time with one simple principle:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An alert should be backed by a source.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The objective is not to build the biggest list possible, but to maintain a database reliable enough for an alert to actually mean something when it appears.&lt;/p&gt;

&lt;h3&gt;
  
  
  A few seconds to look at what we are using
&lt;/h3&gt;

&lt;p&gt;Software security can become extremely complex.&lt;/p&gt;

&lt;p&gt;Here, the principle is intentionally simple.&lt;/p&gt;

&lt;p&gt;You run the checker.&lt;/p&gt;

&lt;p&gt;It looks at what is installed.&lt;/p&gt;

&lt;p&gt;It compares it against its local database.&lt;/p&gt;

&lt;p&gt;If it finds something, it tells you.&lt;/p&gt;

&lt;p&gt;Then you investigate.&lt;/p&gt;

&lt;p&gt;Because before deciding whether a dependency should still be part of your environment, &lt;strong&gt;you first need to know that it deserves your attention.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The project is available here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Palks-Studio/dependency-security-checker" rel="noopener noreferrer"&gt;Dependency Security Checker, GitHub&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://palks-studio.com" rel="noopener noreferrer"&gt;https://palks-studio.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>python</category>
      <category>opensource</category>
      <category>vscode</category>
    </item>
    <item>
      <title>Comparing PDF and XML Data Locally with Python, Open Source</title>
      <dc:creator>Anthony Leignel</dc:creator>
      <pubDate>Sat, 15 Aug 2026 20:40:01 +0000</pubDate>
      <link>https://dev.to/palks_studio/comparing-pdf-and-xml-data-locally-with-python-502n</link>
      <guid>https://dev.to/palks_studio/comparing-pdf-and-xml-data-locally-with-python-502n</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwzsjl5bppxsw67tbrtd0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwzsjl5bppxsw67tbrtd0.png" alt="PDF / XML Comparator, an open source tool for comparing PDF and XML data locally"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A lightweight open source tool for detecting differences between two representations of the same document
&lt;/h2&gt;

&lt;p&gt;A PDF and an XML file can represent the same document while containing different information.&lt;/p&gt;

&lt;p&gt;A value may have been modified in one representation, a reference may be missing, a number may use a different format, or some information may simply exist in one file but not the other.&lt;/p&gt;

&lt;p&gt;When both files become part of the same workflow, checking them manually can quickly become tedious.&lt;/p&gt;

&lt;p&gt;This is why I built &lt;strong&gt;PDF / XML Comparator&lt;/strong&gt;, a small open source Python tool designed to perform a first comparison locally.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Palks-Studio/pdf-xml-comparator" rel="noopener noreferrer"&gt;View PDF / XML Comparator on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;PDF and XML serve very different purposes.&lt;/p&gt;

&lt;p&gt;The PDF provides a human-readable representation of a document, while XML provides structured data that can be processed by software.&lt;/p&gt;

&lt;p&gt;Even when both representations originate from the same process, having two files does not automatically guarantee that every value represented in one can also be found in the other.&lt;/p&gt;

&lt;p&gt;For example, you may encounter situations where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a reference differs between the PDF and XML&lt;/li&gt;
&lt;li&gt;an amount was changed in one representation&lt;/li&gt;
&lt;li&gt;a value exists only in the XML&lt;/li&gt;
&lt;li&gt;information visible in the PDF cannot be found in the XML&lt;/li&gt;
&lt;li&gt;equivalent values use different formatting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Opening both files and searching for every value manually works for a single document, but it quickly becomes inefficient when repeated.&lt;/p&gt;

&lt;h2&gt;
  
  
  A deliberately simple approach
&lt;/h2&gt;

&lt;p&gt;PDF / XML Comparator does not try to understand the business meaning of every field.&lt;/p&gt;

&lt;p&gt;Instead, it performs a bidirectional comparison.&lt;/p&gt;

&lt;p&gt;The process can be summarized as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PDF ──► Text extraction ──► Search values in XML
                              │
                              ▼
                         Differences
                              ▲
                              │
XML ──► Data extraction ──► Search values in PDF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;a href="https://palks-studio.com" rel="noopener noreferrer"&gt;https://palks-studio.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>webdev</category>
      <category>python</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Compare Two PDF Files Locally with Python, Open Source</title>
      <dc:creator>Anthony Leignel</dc:creator>
      <pubDate>Wed, 12 Aug 2026 19:41:42 +0000</pubDate>
      <link>https://dev.to/palks_studio/compare-two-pdf-files-locally-with-python-43il</link>
      <guid>https://dev.to/palks_studio/compare-two-pdf-files-locally-with-python-43il</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3zhqds9qgduded8vuk03.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3zhqds9qgduded8vuk03.png" alt="PDF Comparator" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A lightweight open-source tool for detecting textual differences between PDFs
&lt;/h2&gt;

&lt;p&gt;Comparing two versions of a PDF should not necessarily require uploading documents to an online service.&lt;/p&gt;

&lt;p&gt;This is especially true when working with internal documentation, reports, invoices, technical files, or documents that simply should not leave your machine.&lt;/p&gt;

&lt;p&gt;I built &lt;strong&gt;PDF Comparator&lt;/strong&gt;, a small open-source Python tool designed for exactly that purpose.&lt;/p&gt;

&lt;p&gt;It compares the textual content of two PDF files locally and shows what changed between them.&lt;/p&gt;

&lt;p&gt;No AI, no external API, no account, and no document upload.&lt;/p&gt;




&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;The principle is intentionally simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PDF A → Text extraction
&lt;/li&gt;
&lt;li&gt;PDF B → Text extraction
→ Lightweight normalization
→ Comparison
→ Differences&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first PDF is considered the reference version, while the second is the new version.&lt;/p&gt;

&lt;p&gt;The tool extracts text from all pages, performs lightweight normalization, then detects added and removed lines.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--- old.pdf
+++ new.pdf

-Address: 12 Example Street
+Address: 24 Example Street

-Total: €1,200
+Total: €1,350
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of manually checking two documents line by line, the differences are immediately visible in the terminal.&lt;/p&gt;




&lt;h2&gt;
  
  
  Running it
&lt;/h2&gt;

&lt;p&gt;The project only requires Python 3 and pypdf.&lt;/p&gt;

&lt;p&gt;Clone the repository and install the dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/Palks-Studio/pdf-comparator
cd pdf-comparator

python -m pip install -r requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then compare two PDFs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python pdf-compare.py old.pdf new.pdf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If no textual difference is found, the tool simply reports that no difference was detected.&lt;/p&gt;

&lt;p&gt;Otherwise, the differences are displayed directly in the terminal.&lt;/p&gt;




&lt;h2&gt;
  
  
  Local by design
&lt;/h2&gt;

&lt;p&gt;One of the main reasons I built this tool was to keep the workflow simple and local.&lt;/p&gt;

&lt;p&gt;The PDFs are processed directly on your machine.&lt;/p&gt;

&lt;p&gt;There is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no file upload&lt;/li&gt;
&lt;li&gt;no remote server&lt;/li&gt;
&lt;li&gt;no external API&lt;/li&gt;
&lt;li&gt;no AI processing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For documents containing internal or sensitive information, avoiding an unnecessary external transfer can be just as important as the comparison itself.&lt;/p&gt;




&lt;h2&gt;
  
  
  What it can be useful for
&lt;/h2&gt;

&lt;p&gt;PDF Comparator can help when reviewing different versions of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;technical documentation&lt;/li&gt;
&lt;li&gt;reports&lt;/li&gt;
&lt;li&gt;invoices&lt;/li&gt;
&lt;li&gt;specifications&lt;/li&gt;
&lt;li&gt;administrative documents&lt;/li&gt;
&lt;li&gt;generated PDFs&lt;/li&gt;
&lt;li&gt;archived documents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It can also be useful during development when a system generates PDFs and you want a quick way to check whether textual content changed between two outputs.&lt;/p&gt;




&lt;h2&gt;
  
  
  What it does not do
&lt;/h2&gt;

&lt;p&gt;PDF Comparator is deliberately focused on &lt;strong&gt;textual differences&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It does not compare:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;images&lt;/li&gt;
&lt;li&gt;fonts&lt;/li&gt;
&lt;li&gt;colors&lt;/li&gt;
&lt;li&gt;graphical layout&lt;/li&gt;
&lt;li&gt;visual rendering&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also does not interpret the meaning of a change.&lt;/p&gt;

&lt;p&gt;If a value changes from &lt;code&gt;1200&lt;/code&gt; to &lt;code&gt;1350&lt;/code&gt;, the tool detects the difference, but it does not try to determine whether that change is correct.&lt;/p&gt;

&lt;p&gt;The quality of the comparison also depends on the text layer available inside the PDF. A scanned document containing only images cannot be reliably compared without OCR.&lt;/p&gt;

&lt;p&gt;Keeping this distinction explicit makes the tool predictable: it does one job and stays focused on it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Open source
&lt;/h2&gt;

&lt;p&gt;PDF Comparator is available as an open-source project under the MIT License.&lt;/p&gt;

&lt;p&gt;The repository contains the Python script, requirements, documentation in English and French, and installation instructions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Repository
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/Palks-Studio/pdf-comparator" rel="noopener noreferrer"&gt;View PDF Comparator on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Contributions, feedback, issues and improvements are welcome.&lt;/p&gt;

&lt;p&gt;Sometimes a useful tool does not need a platform, an account, or another service in the middle.&lt;/p&gt;

&lt;p&gt;Two files, one local script, and the differences.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://palks-studio.com" rel="noopener noreferrer"&gt;https://palks-studio.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>opensource</category>
    </item>
    <item>
      <title>What if we stopped explaining technology only through text?</title>
      <dc:creator>Anthony Leignel</dc:creator>
      <pubDate>Sun, 09 Aug 2026 19:16:07 +0000</pubDate>
      <link>https://dev.to/palks_studio/what-if-we-stopped-explaining-technology-only-through-text-37lh</link>
      <guid>https://dev.to/palks_studio/what-if-we-stopped-explaining-technology-only-through-text-37lh</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpzv5f1efauahdryzjon9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpzv5f1efauahdryzjon9.png" alt="Interactive cookie experience interface" width="799" height="386"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  I created interactive experiences to show what really happens behind actions that seem simple
&lt;/h2&gt;

&lt;p&gt;A cookie accepted with one click, two systems connected through an API, a task to automate, user authentication, or even a Factur-X invoice.&lt;/p&gt;

&lt;p&gt;From the outside, many technical actions look simple.&lt;/p&gt;

&lt;p&gt;And that's perfectly normal.&lt;/p&gt;

&lt;p&gt;Users see the result, not necessarily everything that needs to happen behind the scenes for that result to work properly.&lt;/p&gt;

&lt;p&gt;Of course, these mechanisms can be explained through articles, documentation, or diagrams, but not everyone wants to read several pages to understand a topic, some simply don't have the time, while others understand things better when they can visually follow what is happening.&lt;/p&gt;

&lt;p&gt;That's why I started creating a series of interactive experiences.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understand in just a few clicks
&lt;/h2&gt;

&lt;p&gt;The principle is intentionally simple.&lt;/p&gt;

&lt;p&gt;Each experience starts with an action or idea that seems straightforward, then gradually reveals what is actually happening behind the scenes.&lt;/p&gt;

&lt;p&gt;Let's take automation.&lt;/p&gt;

&lt;p&gt;A request can start with something as simple as:&lt;/p&gt;

&lt;p&gt;"Could we automate this task?"&lt;/p&gt;

&lt;p&gt;For the person making the request, it really is just one task.&lt;/p&gt;

&lt;p&gt;But integrating it properly into a system may require analyzing the existing process, defining business rules, validating data, anticipating errors, securing execution, handling repeated runs, testing different scenarios, and making sure the system remains stable over time.&lt;/p&gt;

&lt;p&gt;The experience isn't designed to turn visitors into developers, it simply makes this part of the work visible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Showing what the interface doesn't show
&lt;/h2&gt;

&lt;p&gt;That's the common thread throughout the collection.&lt;/p&gt;

&lt;p&gt;An API can make it look like two systems are simply exchanging data, yet behind a single request there may be authentication, validation, processing, response codes, errors, timeouts, and retries.&lt;/p&gt;

&lt;p&gt;A user login may look like nothing more than an email address and a password, while a properly built application needs to perform several checks before granting access.&lt;/p&gt;

&lt;p&gt;A Factur-X invoice looks like a PDF, but it also contains structured XML data, and both representations need to tell the same story.&lt;/p&gt;

&lt;p&gt;A cookie may seem insignificant, yet it can become the starting point of a much larger chain involving data collection and profiling.&lt;/p&gt;

&lt;p&gt;The goal isn't to make these topics seem artificially complicated or alarming, but to show that between a visible action and its result, there is often an entire system the user never sees.&lt;/p&gt;

&lt;h2&gt;
  
  
  Another way to talk about technology
&lt;/h2&gt;

&lt;p&gt;I like technical articles and documentation, and I publish those too, but I wanted to add a different format.&lt;/p&gt;

&lt;p&gt;Something you can explore quickly, without requiring any particular technical background, where a few clicks are enough to understand an idea.&lt;/p&gt;

&lt;p&gt;The experiences are therefore intentionally short, visual, and progressive.&lt;/p&gt;

&lt;p&gt;Today, the collection covers several areas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;systems and architecture, with from-scratch development, APIs, and automation&lt;/li&gt;
&lt;li&gt;security and data, with application authentication, cookies, and personal data&lt;/li&gt;
&lt;li&gt;electronic invoicing, with Factur-X&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More experiences will be added to the collection, because there are still plenty of technical mechanisms worth making visible this way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Explore the interactive experiences
&lt;/h2&gt;

&lt;p&gt;If you'd rather experience something than read a long explanation, or if you simply want to discover what happens behind some of the mechanisms we use every day, the full collection is available here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://palks-studio.com/en/interactive-experiences" rel="noopener noreferrer"&gt;Explore the interactive experiences&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few clicks are enough, then it's up to you to decide how far you want to follow the journey.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://palks-studio.com" rel="noopener noreferrer"&gt;https://palks-studio.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>security</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>The Palks Studio Approach</title>
      <dc:creator>Anthony Leignel</dc:creator>
      <pubDate>Tue, 04 Aug 2026 14:03:24 +0000</pubDate>
      <link>https://dev.to/palks_studio/-the-palks-studio-approach-300e</link>
      <guid>https://dev.to/palks_studio/-the-palks-studio-approach-300e</guid>
      <description>&lt;h2&gt;
  
  
  Why Palks Studio exists
&lt;/h2&gt;

&lt;p&gt;Palks Studio was created around a simple observation: many software projects become more complex than they need to be.&lt;/p&gt;

&lt;p&gt;New tools are added to solve problems introduced by previous tools. External services become permanent dependencies. Business data is scattered across multiple platforms. Over time, maintenance becomes more expensive than development itself.&lt;/p&gt;

&lt;p&gt;I chose a different approach.&lt;/p&gt;

&lt;p&gt;The goal is not to build software with the largest possible technology stack. The goal is to build systems that remain understandable, maintainable, and reliable years after they are deployed.&lt;/p&gt;

&lt;p&gt;Every project starts with the same question:&lt;/p&gt;

&lt;p&gt;How can this problem be solved with the fewest moving parts while remaining robust, secure, and easy to maintain?&lt;/p&gt;

&lt;p&gt;Sometimes that means using an existing library. Sometimes it means writing a component from scratch. The decision is driven by long-term stability rather than short-term convenience.&lt;/p&gt;

&lt;p&gt;This philosophy shapes every system developed at Palks Studio, from electronic invoicing and business automation to internal tools and backend infrastructures.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://palks-studio.com" rel="noopener noreferrer"&gt;Website&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Palks-Studio" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/@Palks_Studio" rel="noopener noreferrer"&gt;YouTube&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.linkedin.com/in/palks-studio/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.malt.fr/profile/anthonyleignel" rel="noopener noreferrer"&gt;Malt&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Simplicity is an engineering choice
&lt;/h2&gt;

&lt;p&gt;Simple software is often perceived as limited.&lt;/p&gt;

&lt;p&gt;In reality, simplicity is usually the result of deliberate engineering decisions.&lt;/p&gt;

&lt;p&gt;Every additional service, dependency, framework, or external platform increases the number of components that must be deployed, monitored, updated, secured, and maintained over time.&lt;/p&gt;

&lt;p&gt;Keeping an architecture simple is not about avoiding technology. It is about avoiding unnecessary complexity.&lt;/p&gt;

&lt;p&gt;At Palks Studio, each component must have a clear purpose. If a feature can be implemented without introducing another layer of abstraction or another external dependency, that option is considered first.&lt;/p&gt;

&lt;p&gt;The objective is not to build the smallest application possible. It is to build systems that remain understandable, predictable, and maintainable long after the initial development phase.&lt;/p&gt;

&lt;p&gt;A simple architecture is easier to document, easier to audit, easier to debug, and easier to evolve.&lt;/p&gt;

&lt;p&gt;Complexity should come from solving business problems, not from the technical stack itself.&lt;/p&gt;




&lt;h2&gt;
  
  
  Own the infrastructure
&lt;/h2&gt;

&lt;p&gt;Software should not require handing over control of a company's data or infrastructure.&lt;/p&gt;

&lt;p&gt;Whenever possible, the systems developed at Palks Studio are deployed directly on the client's own hosting, whether it is a shared server, a VPS, or dedicated infrastructure.&lt;/p&gt;

&lt;p&gt;This approach gives the client full ownership of their environment. Their data remains under their control, backups stay on their infrastructure, and the system continues to operate independently of any specific software vendor.&lt;/p&gt;

&lt;p&gt;It also simplifies long-term maintenance. There is no proprietary platform to migrate away from, no subscription required to keep the software running, and no dependency on a third-party service that could change its pricing, features, or availability.&lt;/p&gt;

&lt;p&gt;The objective is not to avoid cloud services at all costs. It is to ensure that the client remains in control of the software they rely on every day.&lt;/p&gt;




&lt;h2&gt;
  
  
  Data belongs to the client
&lt;/h2&gt;

&lt;p&gt;Collecting data is easy. Deciding not to collect it requires a different mindset.&lt;/p&gt;

&lt;p&gt;At Palks Studio, systems are designed to keep only the information that is necessary for their intended purpose. If a piece of data has no operational value, it should not be stored.&lt;/p&gt;

&lt;p&gt;The same principle applies to the public website. It operates without advertising trackers, analytics scripts, or unnecessary cookies. Visitors are not profiled, and browsing does not generate data that serves no purpose.&lt;/p&gt;

&lt;p&gt;Keeping less data has practical benefits. It reduces storage requirements, simplifies compliance with privacy regulations, limits the impact of a potential data breach, and makes systems easier to understand and maintain.&lt;/p&gt;

&lt;p&gt;Data should help users achieve their goals. It should not become a product in itself.&lt;/p&gt;

&lt;p&gt;This principle guides every project developed at Palks Studio.&lt;/p&gt;




&lt;h2&gt;
  
  
  Security starts with architecture
&lt;/h2&gt;

&lt;p&gt;Security is often associated with encryption, firewalls, or authentication.&lt;/p&gt;

&lt;p&gt;These are important, but they are only part of the picture.&lt;/p&gt;

&lt;p&gt;A system also becomes more secure by reducing what it exposes.&lt;/p&gt;

&lt;p&gt;Fewer external services mean fewer communication channels.&lt;/p&gt;

&lt;p&gt;Fewer dependencies mean fewer components that require security updates.&lt;/p&gt;

&lt;p&gt;Fewer stored data mean less information that could be compromised.&lt;/p&gt;

&lt;p&gt;Whenever possible, systems are designed with a clear separation between public and private components. Configuration files, business logic, and sensitive data remain outside the public web root, while only the elements intended to be accessed by users are exposed.&lt;/p&gt;

&lt;p&gt;The objective is not to eliminate every risk. It is to reduce the attack surface before the software is even deployed.&lt;/p&gt;

&lt;p&gt;Good security is not only something added to a system.&lt;/p&gt;

&lt;p&gt;It is something built into its architecture from the beginning.&lt;/p&gt;




&lt;h2&gt;
  
  
  Business rules before automation
&lt;/h2&gt;

&lt;p&gt;Automation is often presented as the solution.&lt;/p&gt;

&lt;p&gt;In reality, it only amplifies the quality of the process it executes.&lt;/p&gt;

&lt;p&gt;Automating a poorly designed workflow does not make it better. It simply allows the same mistakes to happen faster and more consistently.&lt;/p&gt;

&lt;p&gt;Before introducing automation, the business rules must be clearly defined:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What should happen?
&lt;/li&gt;
&lt;li&gt;Under which conditions?
&lt;/li&gt;
&lt;li&gt;What data is required?
&lt;/li&gt;
&lt;li&gt;What makes a process valid?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only when these questions have clear answers does automation become valuable.&lt;/p&gt;

&lt;p&gt;This principle applies to every project developed at Palks Studio. Whether generating a Factur-X invoice, processing job applications, or orchestrating a business workflow, the objective is always the same: understand the business logic first, then automate its execution.&lt;/p&gt;

&lt;p&gt;Automation should execute decisions.&lt;/p&gt;

&lt;p&gt;It should not replace them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Systems instead of tools
&lt;/h2&gt;

&lt;p&gt;A tool solves a task.&lt;/p&gt;

&lt;p&gt;A system supports a workflow.&lt;/p&gt;

&lt;p&gt;This distinction shapes every project developed at Palks Studio.&lt;/p&gt;

&lt;p&gt;A billing system is more than invoice generation. It also manages quotations, signatures, payments, structured archives, compliance rules, and future interoperability.&lt;/p&gt;

&lt;p&gt;A recruitment system is more than a form. It evaluates candidates, applies business rules, ranks profiles, manages campaigns, and keeps the recruitment process consistent.&lt;/p&gt;

&lt;p&gt;An automation system is more than a script. It validates data, executes predefined workflows, handles errors, produces traceable outputs, and remains predictable over time.&lt;/p&gt;

&lt;p&gt;The objective is never to automate a single action in isolation.&lt;/p&gt;

&lt;p&gt;It is to build systems that remain coherent as they grow.&lt;/p&gt;

&lt;p&gt;Good software is not defined by the number of features it contains.&lt;/p&gt;

&lt;p&gt;It is defined by the reliability of the workflow it supports.&lt;/p&gt;




&lt;h2&gt;
  
  
  A philosophy that guides every project
&lt;/h2&gt;

&lt;p&gt;Technologies evolve.&lt;/p&gt;

&lt;p&gt;Libraries are replaced.&lt;/p&gt;

&lt;p&gt;Standards change.&lt;/p&gt;

&lt;p&gt;Business requirements grow.&lt;/p&gt;

&lt;p&gt;The principles behind a well-designed system should remain the same.&lt;/p&gt;

&lt;p&gt;Build only what is necessary.&lt;/p&gt;

&lt;p&gt;Keep the architecture understandable.&lt;/p&gt;

&lt;p&gt;Reduce unnecessary dependencies.&lt;/p&gt;

&lt;p&gt;Protect the client's data.&lt;/p&gt;

&lt;p&gt;Design business rules before automating them.&lt;/p&gt;

&lt;p&gt;Create systems that can still be understood, maintained, and trusted years after they are deployed.&lt;/p&gt;

&lt;p&gt;Every project developed at Palks Studio follows these principles.&lt;/p&gt;

&lt;p&gt;They are not constraints.&lt;/p&gt;

&lt;p&gt;They are the foundation.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://palks-studio.com" rel="noopener noreferrer"&gt;https://palks-studio.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>programming</category>
      <category>webdev</category>
      <category>security</category>
    </item>
    <item>
      <title>I Built a Local VS Code Environment That Puts Me Back in Control</title>
      <dc:creator>Anthony Leignel</dc:creator>
      <pubDate>Wed, 01 Jul 2026 17:27:54 +0000</pubDate>
      <link>https://dev.to/palks_studio/i-built-a-local-vs-code-environment-that-saves-me-hours-on-every-project-lpc</link>
      <guid>https://dev.to/palks_studio/i-built-a-local-vs-code-environment-that-saves-me-hours-on-every-project-lpc</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0tuju5l9nxx224fkdqef.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0tuju5l9nxx224fkdqef.png" alt="Before and after comparison showing automatic code reindentation and whitespace cleanup applied to the same source file." width="800" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Built It
&lt;/h2&gt;

&lt;p&gt;For years, I kept adding new tools to my development environment because every extension, formatter, or utility promised to make development faster. At first it worked, but over time my workflow became harder to understand. Files were modified automatically, formatting changed from one project to another, and some actions happened in the background without me even noticing.&lt;/p&gt;

&lt;p&gt;The problem wasn't Visual Studio Code, and it wasn't the extensions themselves. The real problem was that I had gradually stopped controlling my own development environment.&lt;/p&gt;

&lt;p&gt;Instead of installing even more tools, I started removing them. I replaced hidden automation with explicit actions and built a lightweight local environment around a simple principle:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nothing should modify my code unless I decide it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Over time, this approach evolved into what is now my VS Code Environment Pack. Rather than trying to automate everything, it provides a collection of local tools that help me create projects, maintain codebases, generate documentation, keep timestamped backups, and automate repetitive tasks while keeping every action predictable and under my control.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://palks-studio.com/en/vscode-environment-pack" rel="noopener noreferrer"&gt;View Resource&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=NZgXd_sHEy4&amp;amp;list=PLR1GDrpaWT2k" rel="noopener noreferrer"&gt;YouTube playlist&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Palks-Studio/vs-code-environment-pack" rel="noopener noreferrer"&gt;View the GitHub repository&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Every Project Starts the Same Way
&lt;/h2&gt;

&lt;p&gt;One of the first repetitive tasks in any project has nothing to do with writing code.&lt;/p&gt;

&lt;p&gt;Creating folders.&lt;/p&gt;

&lt;p&gt;Creating empty files.&lt;/p&gt;

&lt;p&gt;Reproducing the same directory structure again and again.&lt;/p&gt;

&lt;p&gt;It doesn't take long, but after dozens of projects it becomes unnecessary work.&lt;/p&gt;

&lt;p&gt;Instead of creating everything manually, I describe the project structure once in a simple &lt;code&gt;structure.txt&lt;/code&gt; file. Running a single task generates the complete hierarchy, including every folder and file exactly where it belongs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my_project

    README.md

    app

        app.py

    static

        css

            style.css
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal isn't to generate code.&lt;/p&gt;

&lt;p&gt;It's to start every project from a clean, consistent structure, so I can focus on solving the actual problem instead of rebuilding the same foundation every time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Documentation Should Never Be Manual
&lt;/h2&gt;

&lt;p&gt;Understanding a project often starts with a simple question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How is it organized?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When a codebase grows, answering that question manually becomes surprisingly time-consuming. Taking screenshots, writing folder structures, or listing files by hand quickly becomes outdated.&lt;/p&gt;

&lt;p&gt;That's why I built &lt;code&gt;tree.py&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With a single task, it scans the entire project and generates a &lt;code&gt;PROJECT_TREE.txt&lt;/code&gt; file representing the complete directory structure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my_project/
├── app
│   ├── app.py
│   └── controllers
├── static
│   ├── css
│   └── js
├── README.md
└── requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I use it to document projects, share their architecture, review existing codebases, and quickly understand how a project is organized.&lt;/p&gt;

&lt;p&gt;Like every other tool in this environment, it performs one task, does it locally, and produces a result that is easy to read, reuse, and version.&lt;/p&gt;




&lt;h2&gt;
  
  
  Every Change Is Intentional
&lt;/h2&gt;

&lt;p&gt;One of the principles behind this environment is that no script should modify more files than necessary.&lt;/p&gt;

&lt;p&gt;That's why most maintenance tools can run in three different modes.&lt;/p&gt;

&lt;p&gt;Sometimes I want to clean an entire project before committing it.&lt;/p&gt;

&lt;p&gt;Sometimes I only want to work on the file I'm editing.&lt;/p&gt;

&lt;p&gt;And sometimes I need to target a small selection of files without touching anything else.&lt;/p&gt;

&lt;p&gt;Instead of forcing a single workflow, the environment adapts to the situation.&lt;/p&gt;

&lt;p&gt;The same approach applies to cleaning whitespace, converting line endings, and other maintenance tasks. Every operation can be executed on the whole project, the active file, or a custom selection.&lt;/p&gt;

&lt;p&gt;Nothing runs automatically in the background.&lt;/p&gt;

&lt;p&gt;Nothing changes unless I ask for it.&lt;/p&gt;

&lt;p&gt;That level of control makes maintenance safer, especially on large projects where a single unintended modification can affect hundreds of files.&lt;/p&gt;




&lt;h2&gt;
  
  
  Every Save Creates a Safety Net
&lt;/h2&gt;

&lt;p&gt;Making changes is part of development.&lt;/p&gt;

&lt;p&gt;Deleting the wrong block, trying a different implementation, or restructuring a file happens every day. Most of the time, version control is enough. Sometimes, it isn't.&lt;/p&gt;

&lt;p&gt;I wanted something much simpler.&lt;/p&gt;

&lt;p&gt;Every time I save a file, a timestamped copy is created automatically inside a local backup directory. Nothing is uploaded, nothing is synchronized, and nothing depends on an external extension or service.&lt;/p&gt;

&lt;p&gt;If I realize a mistake a few minutes later, I don't need to search through Git commits or recover a deleted file. The previous version is already there.&lt;/p&gt;

&lt;p&gt;More importantly, this changes the way I work.&lt;/p&gt;

&lt;p&gt;I can experiment more freely because I know every saved version still exists. Refactoring becomes less stressful, trying a new approach becomes easier, and recovering from a mistake takes seconds instead of minutes.&lt;/p&gt;

&lt;p&gt;The backup system isn't just about protecting files.&lt;/p&gt;

&lt;p&gt;It's about giving me the confidence to change them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Small Tools, One Responsibility
&lt;/h2&gt;

&lt;p&gt;One design decision guided the entire project: every tool should solve one problem, and solve it well.&lt;/p&gt;

&lt;p&gt;Instead of building one large application responsible for formatting, backups, project generation, documentation, and file maintenance, I chose to keep every feature independent.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;create.py&lt;/code&gt; creates projects.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tree.py&lt;/code&gt; documents them.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;clean.py&lt;/code&gt; removes unnecessary whitespace.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;space.py&lt;/code&gt; reports formatting issues without modifying anything.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;convert.py&lt;/code&gt; normalizes line endings.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;backup.py&lt;/code&gt; keeps timestamped copies of every saved file.&lt;/p&gt;

&lt;p&gt;Each tool can evolve independently, remain easy to understand, and be reused in other projects without bringing along features that aren't needed.&lt;/p&gt;

&lt;p&gt;The result isn't just easier to maintain.&lt;/p&gt;

&lt;p&gt;It's easier to trust.&lt;/p&gt;

&lt;p&gt;When a tool has one responsibility, you always know why you're running it and exactly what it will do.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This project didn't start as a VS Code pack.&lt;/p&gt;

&lt;p&gt;It started with a simple question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How can I build an environment that stays predictable, even as my projects become larger and more complex?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Over time, the answer became a collection of small local tools designed to solve everyday problems without introducing unnecessary complexity.&lt;/p&gt;

&lt;p&gt;Creating projects.&lt;/p&gt;

&lt;p&gt;Documenting architectures.&lt;/p&gt;

&lt;p&gt;Maintaining codebases.&lt;/p&gt;

&lt;p&gt;Keeping local backups.&lt;/p&gt;

&lt;p&gt;Cleaning and formatting files.&lt;/p&gt;

&lt;p&gt;Every tool has a single responsibility, every action is explicit, and every modification remains under my control.&lt;/p&gt;

&lt;p&gt;Today, this environment is part of every project I build.&lt;/p&gt;

&lt;p&gt;Not because it automates everything, but because it automates the right things while letting me decide when and how they happen.&lt;/p&gt;

&lt;p&gt;For me, that's what a good development environment should do.&lt;/p&gt;

&lt;p&gt;It shouldn't write code for the developer.&lt;/p&gt;

&lt;p&gt;It should help the developer write better code with confidence.&lt;/p&gt;




&lt;h2&gt;
  
  
  Go Further
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://palks-studio.com/en/content" rel="noopener noreferrer"&gt;Explore more technical articles&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://palks-studio.com/en/technical-notes" rel="noopener noreferrer"&gt;Read more technical notes&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://palks-studio.com" rel="noopener noreferrer"&gt;https://palks-studio.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>vscode</category>
      <category>webdev</category>
      <category>python</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I Built a Local Data Collection System in Python</title>
      <dc:creator>Anthony Leignel</dc:creator>
      <pubDate>Thu, 04 Jun 2026 17:13:36 +0000</pubDate>
      <link>https://dev.to/palks_studio/i-built-a-local-data-collection-system-in-python-3k6p</link>
      <guid>https://dev.to/palks_studio/i-built-a-local-data-collection-system-in-python-3k6p</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fikdp3uzo5b1idyprjcnc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fikdp3uzo5b1idyprjcnc.png" alt="Results interface showing collected prospects with columns for name, website, email, phone, city, country, and a checkbox to mark contacted leads; includes filters and export options" width="800" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Built It
&lt;/h2&gt;

&lt;p&gt;Most data collection and lead generation tools are delivered as SaaS products.&lt;/p&gt;

&lt;p&gt;You create an account, subscribe, send your data to a third-party platform and keep paying every month to continue using the tool.&lt;/p&gt;

&lt;p&gt;I wanted a different approach.&lt;/p&gt;

&lt;p&gt;I built a fully local data collection system that runs on the user's machine, without subscriptions, without paid APIs and without relying on external platforms.&lt;/p&gt;

&lt;p&gt;The goal was simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create searches
&lt;/li&gt;
&lt;li&gt;Collect data from multiple sources
&lt;/li&gt;
&lt;li&gt;Clean and validate the results
&lt;/li&gt;
&lt;li&gt;Export usable data
&lt;/li&gt;
&lt;li&gt;Keep everything under the user's control&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://palks-studio.com/en/data-collection" rel="noopener noreferrer"&gt;View System&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=W12Njqldd7s" rel="noopener noreferrer"&gt;Watch the Complete Demo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Palks-Studio/data-collection-system" rel="noopener noreferrer"&gt;View the GitHub repository&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What the System Does
&lt;/h2&gt;

&lt;p&gt;The application allows users to create and manage searches from a web interface.&lt;/p&gt;

&lt;p&gt;For each search, users can define:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A search name
&lt;/li&gt;
&lt;li&gt;A keyword
&lt;/li&gt;
&lt;li&gt;A city
&lt;/li&gt;
&lt;li&gt;A country
&lt;/li&gt;
&lt;li&gt;The search engines to use&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The system currently supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DuckDuckGo
&lt;/li&gt;
&lt;li&gt;Bing
&lt;/li&gt;
&lt;li&gt;Qwant&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once a search is executed, the collection engine starts gathering information from the selected sources.&lt;/p&gt;




&lt;h2&gt;
  
  
  Data Processing Pipeline
&lt;/h2&gt;

&lt;p&gt;The system follows a structured workflow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Load Configuration
↓
Initialize Environment
↓
Load Searches
↓
Run Collectors
↓
Clean Data
↓
Normalize Data
↓
Validate Records
↓
Remove Duplicates
↓
Generate Exports
↓
Save Results
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each component has a single responsibility.&lt;/p&gt;

&lt;p&gt;Collectors collect data.&lt;/p&gt;

&lt;p&gt;Processors clean and validate it.&lt;/p&gt;

&lt;p&gt;Exporters generate output files.&lt;/p&gt;

&lt;p&gt;The main engine orchestrates the workflow.&lt;/p&gt;




&lt;h2&gt;
  
  
  Extracting Business Information
&lt;/h2&gt;

&lt;p&gt;The system doesn't stop at search engine results.&lt;/p&gt;

&lt;p&gt;When a website is discovered, the application can visit the site and extract useful information such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Website URL
&lt;/li&gt;
&lt;li&gt;Email addresses
&lt;/li&gt;
&lt;li&gt;Phone numbers
&lt;/li&gt;
&lt;li&gt;Company name
&lt;/li&gt;
&lt;li&gt;Location information&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The collected data is then normalized and validated before being added to the final dataset.&lt;/p&gt;




&lt;h2&gt;
  
  
  Managing Contacted Leads
&lt;/h2&gt;

&lt;p&gt;One feature I wanted from the beginning was lead tracking.&lt;/p&gt;

&lt;p&gt;Users can mark prospects as contacted directly from the interface.&lt;/p&gt;

&lt;p&gt;The information is stored locally and remains available after closing the application.&lt;/p&gt;

&lt;p&gt;This makes it easy to distinguish:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New prospects
&lt;/li&gt;
&lt;li&gt;Already contacted prospects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;without relying on an external CRM.&lt;/p&gt;




&lt;h2&gt;
  
  
  Exporting Data
&lt;/h2&gt;

&lt;p&gt;Once processing is complete, results can be exported as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CSV
&lt;/li&gt;
&lt;li&gt;JSON&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The exported files are ready to be imported into other systems or used for further analysis.&lt;/p&gt;




&lt;h2&gt;
  
  
  Local First
&lt;/h2&gt;

&lt;p&gt;One of the main design goals was independence.&lt;/p&gt;

&lt;p&gt;The system runs locally.&lt;/p&gt;

&lt;p&gt;There is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No SaaS
&lt;/li&gt;
&lt;li&gt;No subscription
&lt;/li&gt;
&lt;li&gt;No third-party account
&lt;/li&gt;
&lt;li&gt;No paid API dependency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The user owns the software and the collected data.&lt;/p&gt;




&lt;h2&gt;
  
  
  Technical Stack
&lt;/h2&gt;

&lt;p&gt;The project is built with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python
&lt;/li&gt;
&lt;li&gt;Flask
&lt;/li&gt;
&lt;li&gt;Playwright
&lt;/li&gt;
&lt;li&gt;BeautifulSoup
&lt;/li&gt;
&lt;li&gt;Requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The interface is served locally through Flask and can be accessed from a browser.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;This project started as a simple data collection tool and gradually evolved into a complete workflow capable of collecting, processing and exporting structured business data.&lt;/p&gt;

&lt;p&gt;Building it locally introduced some interesting challenges around browser automation, data normalization, validation and architecture design.&lt;/p&gt;

&lt;p&gt;The result is a modular system that can be extended with new collectors, processors and export formats without changing the overall architecture.&lt;/p&gt;

&lt;p&gt;For me, the most important aspect remains simple:&lt;/p&gt;

&lt;p&gt;The software belongs to the user and the data never has to leave the machine.&lt;/p&gt;




&lt;h2&gt;
  
  
  Go Further
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://palks-studio.com/en/content" rel="noopener noreferrer"&gt;Explore more technical articles&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://palks-studio.com/en/technical-notes" rel="noopener noreferrer"&gt;Read more technical notes&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://palks-studio.com" rel="noopener noreferrer"&gt;https://palks-studio.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>productivity</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>I Built a Full Recruitment System in PHP: No Database, No SaaS, No Subscription</title>
      <dc:creator>Anthony Leignel</dc:creator>
      <pubDate>Fri, 15 May 2026 19:53:59 +0000</pubDate>
      <link>https://dev.to/palks_studio/i-built-a-full-recruitment-system-in-php-no-database-no-saas-no-subscription-ff1</link>
      <guid>https://dev.to/palks_studio/i-built-a-full-recruitment-system-in-php-no-database-no-saas-no-subscription-ff1</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fayyv322ema09dkzlmf73.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fayyv322ema09dkzlmf73.png" alt="CANDIDATE_FLOW recruiter dashboard — applications ranked by matching score" width="800" height="327"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Automatic candidate scoring, recruiter dashboard, multi-campaign management. Deployed on any standard Apache hosting.
&lt;/h2&gt;

&lt;p&gt;After building a complete invoicing system the same way, I applied the same philosophy to recruitment: no SaaS dependency, no monthly subscription, no data leaving the client's server.&lt;/p&gt;

&lt;p&gt;The result is &lt;strong&gt;CANDIDATE_SYSTEM&lt;/strong&gt;, a self-hosted recruitment engine that automatically scores applicants against a job profile, ranks them by matching score, and lets the recruiter focus on the top profiles instead of reading through dozens of CVs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://palks-studio.com/en/recruitment-without-saas" rel="noopener noreferrer"&gt;View System&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=TNNa5O3VrhA&amp;amp;list=PLP79X9pb3KlU" rel="noopener noreferrer"&gt;Watch the Complete Demo Playlist&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Palks-Studio/candidat-system" rel="noopener noreferrer"&gt;View the GitHub repository&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Problem With Existing Tools
&lt;/h2&gt;

&lt;p&gt;Most recruitment platforms follow the same model: you pay monthly, your data sits on their servers, and the day you stop paying, everything disappears.&lt;/p&gt;

&lt;p&gt;For small companies, DSIs, and independent recruiters, this makes no sense. They don't need a 300€/month SaaS. They need a simple, reliable tool that works on their own hosting.&lt;/p&gt;




&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;The system is split into two parts:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Public&lt;/strong&gt;: the application form (&lt;code&gt;/candidat/&lt;/code&gt;)
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Private&lt;/strong&gt;: configuration, scoring engine, candidate data (outside the webroot)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a candidate submits the form, the scoring engine calculates a final score based on three nested levels:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contribution = scores[answer] × (weight / 100) × (global_weight / 100)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Level 1&lt;/strong&gt;: &lt;code&gt;global_weight&lt;/code&gt;, how much a section counts in the final score.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Level 2&lt;/strong&gt;: &lt;code&gt;weight&lt;/code&gt;, how much a question counts within its section.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Level 3&lt;/strong&gt;: &lt;code&gt;scores&lt;/code&gt;, the raw value assigned to each possible answer (0 to 100).&lt;/p&gt;

&lt;p&gt;Every candidate gets a score on submission. The dashboard displays them ranked from highest to lowest.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Scoring Engine
&lt;/h2&gt;

&lt;p&gt;The scoring logic lives in a single &lt;code&gt;$coring&lt;/code&gt; class. It reads the campaign configuration files, questions with their weights and scores, and the job profile, then runs the calculation across all sections.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$reponses&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;questions&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$question&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$score_brut&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getScoreBrut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$reponses&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$question&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;]]);&lt;/span&gt;
        &lt;span class="nv"&gt;$contribution&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$score_brut&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$question&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'weight'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$detail&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$question&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'section'&lt;/span&gt;&lt;span class="p"&gt;]][&lt;/span&gt;&lt;span class="s1"&gt;'score_section'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nv"&gt;$contribution&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$detail&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$section_id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;$section&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$section&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'contribution'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$section&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'score_section'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$section&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'global_weight'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$score_final&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nv"&gt;$section&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'contribution'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Apply penalties&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;profil&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'malus'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$question_id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$penalties&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$penalties&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$reponses&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$question_id&lt;/span&gt;&lt;span class="p"&gt;]]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$malus&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nv"&gt;$penalties&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$reponses&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$question_id&lt;/span&gt;&lt;span class="p"&gt;]];&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'score_final'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$score_final&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;$malus&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tech stack question (&lt;code&gt;checkbox&lt;/code&gt; type) is scored differently, each selected technology is matched against the scores defined in the question itself. No hardcoded list, fully configurable from the admin interface.&lt;/p&gt;




&lt;h2&gt;
  
  
  Multi-Campaign Architecture
&lt;/h2&gt;

&lt;p&gt;Each campaign is completely independent: its own questions, its own job profile, its own candidate data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;campaigns/
└── [slug]/
    ├── config/          → questions, scores, job profile
    └── data/            → applications, uploads, lock file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creating a new campaign from the dashboard copies the templates, generates the folder structure, and redirects immediately to the new campaign. One click, ready to use.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;campaign.lock&lt;/code&gt; file controls whether the form is open or closed. The recruiter can close a campaign (which purges all candidate data and sends a closure email to every applicant), then reopen it later from the dashboard.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Admin Interface
&lt;/h2&gt;

&lt;p&gt;The recruiter configures everything from the admin panel, no file editing required:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Job profile&lt;/strong&gt;: position title, mission type, location, salary range
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Questions&lt;/strong&gt;: edit labels, answers, scores, section weights; add or delete questions
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Section weights&lt;/strong&gt;: real-time total indicator (must reach 100%)
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Penalty rules&lt;/strong&gt;: attach negative scores to specific answers on specific questions
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Settings&lt;/strong&gt;: sender name, email, site URL, dashboard password&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The section weight total updates in real time as you type. If it doesn't add up to 100%, the indicator turns red.&lt;/p&gt;




&lt;h2&gt;
  
  
  No Database: Just JSON Files
&lt;/h2&gt;

&lt;p&gt;Every application is stored as a flat JSON file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"20260514_143000_abc123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-05-14 14:30:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"score_final"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;74.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"score_label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Good fit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"score_detail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"terrain"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"score_section"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;68.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"contribution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;40.8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"classic"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"score_section"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;75.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"contribution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;15.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"reponses"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"trigger_fields"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No ORM, no migrations, no connection pooling. The dashboard loads all JSON files from the &lt;code&gt;applications/&lt;/code&gt; folder, sorts them by &lt;code&gt;score_final&lt;/code&gt;, and renders the list.&lt;/p&gt;

&lt;p&gt;It's fast enough for the volumes this tool is designed for, typically 20 to 200 applications per campaign.&lt;/p&gt;




&lt;h2&gt;
  
  
  Security
&lt;/h2&gt;

&lt;p&gt;A few things worth noting:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CSRF token&lt;/strong&gt; on the application form, generated per session and validated on submission
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strict public/private separation&lt;/strong&gt;, no configuration file is accessible via the web
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicate prevention&lt;/strong&gt;, one email per campaign, checked on every submission
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Upload validation&lt;/strong&gt;, PDF only, 5 MB max, with extension and MIME type verified
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTP security headers&lt;/strong&gt; on every page (X-Frame-Options, X-Content-Type-Options, Referrer-Policy)
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session required&lt;/strong&gt; for the dashboard, exports, application viewing, and campaign closure&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conditional Form Fields
&lt;/h2&gt;

&lt;p&gt;Some questions trigger additional fields depending on the answer. For example, answering "Yes" to "Do you have a portfolio?" reveals a required URL field. Answering "Yes" to "Any certifications?" triggers a PDF upload.&lt;/p&gt;

&lt;p&gt;The trigger configuration lives in the questions config file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"portfolio"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"trigger"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Oui"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"field_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"portfolio_url"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"field_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"field_label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Portfolio URL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the frontend, a small JS handler watches radio inputs and toggles the conditional field visibility. On the backend, &lt;code&gt;handler.php&lt;/code&gt; only validates the conditional field if the triggering answer was given.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Gets Delivered
&lt;/h2&gt;

&lt;p&gt;When deployed for a client:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full system installed on their hosting
&lt;/li&gt;
&lt;li&gt;First campaign configured with their job profile
&lt;/li&gt;
&lt;li&gt;Admin interface ready to use
&lt;/li&gt;
&lt;li&gt;Technical documentation + user guide
&lt;/li&gt;
&lt;li&gt;Color customization included&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No recurring cost. No dependency on external infrastructure. One flat fee.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;PHP 8.x: no framework, no Composer
&lt;/li&gt;
&lt;li&gt;Apache + &lt;code&gt;.htaccess&lt;/code&gt;: URL rewriting, security headers, directory protection
&lt;/li&gt;
&lt;li&gt;Vanilla JS: form validation, clipboard API, accordion UI
&lt;/li&gt;
&lt;li&gt;JSON flat files, zero database&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Go Further
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://palks-studio.com/en/content" rel="noopener noreferrer"&gt;Explore more technical articles&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://palks-studio.com/en/technical-notes" rel="noopener noreferrer"&gt;Read more technical notes&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://palks-studio.com" rel="noopener noreferrer"&gt;https://palks-studio.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>architecture</category>
      <category>php</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I built a fully automated Factur-X EN16931 batch pipeline in PHP: no SaaS, no agent, just cron and strict business logic</title>
      <dc:creator>Anthony Leignel</dc:creator>
      <pubDate>Wed, 13 May 2026 14:04:09 +0000</pubDate>
      <link>https://dev.to/palks_studio/i-built-a-fully-automated-factur-x-en16931-batch-pipeline-in-php-no-saas-no-agent-just-cron-and-c84</link>
      <guid>https://dev.to/palks_studio/i-built-a-fully-automated-factur-x-en16931-batch-pipeline-in-php-no-saas-no-agent-just-cron-and-c84</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzervxlynz5holook20rv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzervxlynz5holook20rv.png" alt="Automated Factur-X batch pipeline — full monthly cycle from CSV deposit to secured ZIP delivery, no SaaS, no database" width="799" height="531"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How a Monthly Cron Cycle Replaces an Entire Invoicing Platform
&lt;/h2&gt;

&lt;p&gt;The goal wasn't to automate individual tasks.&lt;/p&gt;

&lt;p&gt;It was to automate an entire monthly invoicing cycle.&lt;/p&gt;

&lt;p&gt;From CSV import to Factur-X generation, payment tracking, secure delivery, and revenue reporting, every step runs through the same deterministic pipeline.&lt;/p&gt;

&lt;p&gt;No SaaS.&lt;/p&gt;

&lt;p&gt;No database.&lt;/p&gt;

&lt;p&gt;No background service.&lt;/p&gt;

&lt;p&gt;Just PHP, cron, and business rules.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Palks-Studio/automation-system" rel="noopener noreferrer"&gt;View the GitHub repository&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why I built it this way
&lt;/h2&gt;

&lt;p&gt;The requirement was simple on paper: take client data every month, generate Factur-X EN16931 compliant invoices, track payments, and deliver everything securely.&lt;/p&gt;

&lt;p&gt;The constraint was harder: no SaaS dependency, no shared infrastructure, no implicit processing. Every step had to be explicit, traceable, and stoppable.&lt;/p&gt;

&lt;p&gt;That constraint shaped every architectural decision.&lt;/p&gt;




&lt;h2&gt;
  
  
  The monthly cycle
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Onboarding
&lt;/h3&gt;

&lt;p&gt;Before the first invoice is ever generated, every client is initialized automatically from a form submission. A unique identifier is generated, configuration files are created, archiving folders are structured, and payment tracking is prepared. No manual intervention required. The system is ready to process.&lt;/p&gt;

&lt;h3&gt;
  
  
  D1 to D7: CSV deposit and batch generation
&lt;/h3&gt;

&lt;p&gt;Each client deposits their billing data via a secured form. The system validates structure and formats strictly, with no implicit tolerance and no silent correction. A malformed field stops the entire client batch, isolates it, and triggers an automatic notification for correction before reprocessing.&lt;/p&gt;

&lt;p&gt;If valid, batch generation runs daily via cron. Invoice composition is driven entirely by business rules, including rates, VAT zones, service periods, and multi-line and multi-month billing. The output is a Factur-X EN16931-compliant PDF/A-3 document with embedded XML, making it both human-readable and machine-readable.&lt;/p&gt;

&lt;h3&gt;
  
  
  D8: B2B invoice and automatic pricing tier
&lt;/h3&gt;

&lt;p&gt;On day 8, the system detects the volume of invoices generated during the period and derives the applicable pricing tier automatically. The monthly B2B invoice is then generated and sent by email with the PDF attached. No manual input. The cron runs, the logic decides.&lt;/p&gt;

&lt;h3&gt;
  
  
  D13 to D14: The only manual step
&lt;/h3&gt;

&lt;p&gt;This is the single human touchpoint in the entire cycle: recording received bank transfers. Everything else is automated. This separation is intentional, because payment validation is a business decision, not a technical one.&lt;/p&gt;

&lt;h3&gt;
  
  
  D15: Balance recalculation and payment control
&lt;/h3&gt;

&lt;p&gt;Two operations run on day 15. First, client balances are recalculated from recorded payments. Unpaid clients are automatically deactivated, blocking any further send operations until the situation is resolved. Second, paid receipts are sent to clients with confirmed payment status via cron.&lt;/p&gt;

&lt;h3&gt;
  
  
  D16: Secured delivery
&lt;/h3&gt;

&lt;p&gt;The monthly ZIP is sent once. A deduplication guard ensures nothing can be sent twice, regardless of cron retries or manual triggers. Delivery uses a temporary token, making the file accessible only through a time-limited secured link. The archive is retained independently.&lt;/p&gt;




&lt;h2&gt;
  
  
  What makes it deterministic
&lt;/h2&gt;

&lt;p&gt;Every processing decision is explicit. There is no magic, no silent fallback, no implicit correction.&lt;/p&gt;

&lt;p&gt;If a CSV is invalid the client batch stops.&lt;br&gt;&lt;br&gt;
If a client is unpaid sends are blocked.&lt;br&gt;&lt;br&gt;
If a ZIP was already sent the guard refuses the duplicate.&lt;/p&gt;

&lt;p&gt;Every operation is timestamped in technical logs. Client state is controlled via explicit flags. The revenue journal is updated on every confirmed payment.&lt;/p&gt;

&lt;p&gt;The system is designed to be readable by anyone who picks it up. Not clever. Predictable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;PHP 8: backend logic and cron execution
&lt;/li&gt;
&lt;li&gt;mPDF: PDF generation
&lt;/li&gt;
&lt;li&gt;factur-x (Python, Akretion): Factur-X XML injection and PDF/A-3 conversion
&lt;/li&gt;
&lt;li&gt;Flat-file storage: contracts, invoices, counters, logs, revenues
&lt;/li&gt;
&lt;li&gt;No database. No SaaS. No external API dependency&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;Building this pipeline wasn't about automating invoice generation.&lt;/p&gt;

&lt;p&gt;It was about designing a system where every execution produces the same predictable result.&lt;/p&gt;

&lt;p&gt;Once the business rules were defined, cron simply became the engine that executes them.&lt;/p&gt;

&lt;p&gt;Automation wasn't the goal.&lt;/p&gt;

&lt;p&gt;Reliability was.&lt;/p&gt;




&lt;h2&gt;
  
  
  Go Further
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://palks-studio.com/en/content" rel="noopener noreferrer"&gt;Explore more technical articles&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://palks-studio.com/en/technical-notes" rel="noopener noreferrer"&gt;Read more technical notes&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://palks-studio.com" rel="noopener noreferrer"&gt;https://palks-studio.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>productivity</category>
      <category>programming</category>
      <category>php</category>
    </item>
    <item>
      <title>How I Integrated Factur-X EN16931 into an Existing PHP Billing System</title>
      <dc:creator>Anthony Leignel</dc:creator>
      <pubDate>Mon, 11 May 2026 10:17:35 +0000</pubDate>
      <link>https://dev.to/palks_studio/-how-i-integrated-factur-x-en16931-into-a-php-billing-system-without-a-database-or-saas-25ab</link>
      <guid>https://dev.to/palks_studio/-how-i-integrated-factur-x-en16931-into-a-php-billing-system-without-a-database-or-saas-25ab</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3bffajxx80sck12uae6f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3bffajxx80sck12uae6f.png" alt="A Factur-X EN16931 compliant invoice — PDF/A-3 with embedded XML, generated on-premise without SaaS or ERP" width="800" height="1131"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrating Factur-X into an Existing PHP Application
&lt;/h2&gt;

&lt;p&gt;As the French e-invoicing reform (RFE) moved closer to implementation, I needed to make my billing system compliant without replacing the existing application. Rather than switching to a SaaS platform or adopting an ERP, I chose to integrate the Factur-X standard directly into an existing PHP application running on a standard Apache server.&lt;/p&gt;

&lt;p&gt;This article explains the architecture I built, what the standard actually requires, and the technical challenges I encountered along the way.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is Factur-X?
&lt;/h2&gt;

&lt;p&gt;Factur-X is a Franco-German standard for structured electronic invoicing that combines two elements into a single document:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A human-readable PDF/A-3 invoice
&lt;/li&gt;
&lt;li&gt;An embedded XML document compliant with the EN16931 European standard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both are required. The PDF is intended for people, while the XML is intended for accounting software and automated processing. Together, they form a single electronic invoice.&lt;/p&gt;

&lt;p&gt;Although Factur-X originated in France and Germany, it aligns with the European EN16931 standard and is part of the broader transition toward mandatory e-invoicing across Europe.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://palks-studio.com/en/facturx-invoice-generation" rel="noopener noreferrer"&gt;Try it&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=gboIdyW5als&amp;amp;list=PLZEg9_eUFTGA" rel="noopener noreferrer"&gt;Watch the Complete Demo Playlist&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Palks-Studio/billing-system" rel="noopener noreferrer"&gt;View the GitHub repository&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The VAT Legal Mentions, the Part Most Developers Miss
&lt;/h2&gt;

&lt;p&gt;Generating a valid Factur-X file is only part of the job. The invoice itself must also include the correct VAT legal mention according to the client's location. Getting this wrong isn't a formatting mistake, it's a fiscal compliance issue.&lt;/p&gt;

&lt;p&gt;When the issuer is based in France, there are three possible situations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;French client&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The issuer is a micro-enterprise or is not subject to VAT. The invoice must include the following legal mention:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TVA non applicable, art. 293B du CGI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No VAT is applied, no VAT column appears on the invoice, and the legal mention is mandatory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EU client (B2B with a VAT number)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The transaction falls under the reverse charge mechanism, meaning the customer declares the VAT in their own country. The invoice must include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Autoliquidation – TVA due par le preneur, art. 283-2 du CGI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;VAT is set to 0% and the legal mention replaces the usual VAT information.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Non-EU client&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The service is exported outside the European Union. VAT exemption applies under the French tax code, and the invoice must include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Exonération de TVA – art. 262 I du CGI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, VAT is set to 0%, but the legal mention remains mandatory.&lt;/p&gt;

&lt;p&gt;These aren't optional notes added for convenience. They explain why VAT is absent or transferred, and using the wrong one can become a compliance issue during a tax audit.&lt;/p&gt;

&lt;p&gt;In my billing system, the correct legal mention is selected automatically from the client's ISO country code and VAT number. The developer doesn't choose it manually. The business rules do.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Technical Chain: How Factur-X Is Actually Generated
&lt;/h2&gt;

&lt;p&gt;Generating a Factur-X invoice is split into two distinct phases, each handled by a dedicated component. This separation is intentional because XML generation and PDF/A-3 conversion solve different problems and have different technical requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 1: Building the XML
&lt;/h3&gt;

&lt;p&gt;The first step is handled by a PHP component responsible for generating the EN16931-compliant XML document.&lt;/p&gt;

&lt;p&gt;It reads the invoice data, including issuer and client details, service lines, VAT amounts, totals, and payment terms, then maps every value to its corresponding EN16931 business term. The generated document follows the Factur-X Comfort profile, covering the information required for standard B2B invoicing.&lt;/p&gt;

&lt;p&gt;Several business rules must be respected during this phase:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Amounts use strict decimal formatting with no implicit rounding
&lt;/li&gt;
&lt;li&gt;VAT is aggregated by rate rather than by invoice line
&lt;/li&gt;
&lt;li&gt;Seller and buyer identifiers (SIREN, SIRET, VAT number) are placed in the appropriate XML elements according to the client's location
&lt;/li&gt;
&lt;li&gt;The VAT legal mention determined earlier is written as structured XML data, not only as visible text in the PDF&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The generated XML is stored temporarily before the second phase begins.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 2: Injecting the XML into the PDF
&lt;/h3&gt;

&lt;p&gt;The second step is handled by a Python component using the Factur-X library developed by Akretion, one of the organizations behind the standard.&lt;/p&gt;

&lt;p&gt;Its role is straightforward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read the PDF generated by the PHP billing engine using mPDF
&lt;/li&gt;
&lt;li&gt;Embed the EN16931 XML document
&lt;/li&gt;
&lt;li&gt;Convert the final document to the PDF/A-3b format required by Factur-X&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I chose Python for this stage because the Factur-X library provides reliable PDF/A-3 conversion and XML embedding without introducing heavy PHP dependencies. It also validates the generated XML against the EN16931 schemas before embedding it, providing an additional compliance check.&lt;/p&gt;

&lt;p&gt;The result is a single PDF/A-3 document containing the embedded XML, fully compliant with the Factur-X EN16931 Comfort profile.&lt;/p&gt;

&lt;h3&gt;
  
  
  Temporary Files
&lt;/h3&gt;

&lt;p&gt;The XML only exists for as long as it is needed.&lt;/p&gt;

&lt;p&gt;It is written to a dedicated temporary directory, embedded into the PDF during the second phase, then deleted automatically once the final document has been generated. Only the completed Factur-X invoice is retained.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Architecture Delivers
&lt;/h2&gt;

&lt;p&gt;The result of this pipeline is a single document that is both human-readable and machine-readable: a Factur-X EN16931-compliant PDF/A-3 containing its embedded XML, ready for transmission to a French Partner Dematerialization Platform (PDP) or direct exchange with a client.&lt;/p&gt;

&lt;p&gt;Beyond compliance, this architecture provides several practical advantages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Self-Hosted by Design
&lt;/h3&gt;

&lt;p&gt;The entire generation pipeline runs on the same server as the billing system. No external API is called during invoice generation, and no financial data leaves the infrastructure.&lt;/p&gt;

&lt;p&gt;For organizations that require full control over their accounting data, this removes the dependency on third-party cloud services during document generation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Independent from an ERP
&lt;/h3&gt;

&lt;p&gt;The system doesn't rely on an ERP or existing accounting software.&lt;/p&gt;

&lt;p&gt;Invoices are generated directly from the billing workflow, using the client information, service lines, VAT rules, and payment terms already managed by the application.&lt;/p&gt;

&lt;p&gt;This makes the solution suitable for businesses that need Factur-X compliance without replacing their existing processes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Compliance Built into the Workflow
&lt;/h3&gt;

&lt;p&gt;Compliance isn't added at the end of the process.&lt;/p&gt;

&lt;p&gt;VAT rates, legal mentions, and business rules are determined before the document is generated, then represented consistently in both the visible PDF and the embedded XML.&lt;/p&gt;

&lt;p&gt;The result isn't simply a document that looks compliant.&lt;/p&gt;

&lt;p&gt;It's a workflow designed to produce compliant invoices by construction.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Integrating Factur-X into an existing billing system isn't only about generating XML or converting a PDF to PDF/A-3.&lt;/p&gt;

&lt;p&gt;The real challenge is translating business rules into structured data before the invoice is created.&lt;/p&gt;

&lt;p&gt;The architecture described here, PHP for XML generation and Python for PDF/A-3 conversion and XML embedding, has allowed me to integrate Factur-X into an existing application without introducing an ERP, a SaaS platform, or unnecessary complexity.&lt;/p&gt;

&lt;p&gt;If you're working on a similar integration, I hope this overview helps clarify where the real challenges lie.&lt;/p&gt;

&lt;p&gt;Once the business rules are modeled correctly, generating a compliant Factur-X invoice becomes the easiest part of the process.&lt;/p&gt;




&lt;h2&gt;
  
  
  Go Further
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://palks-studio.com/en/content" rel="noopener noreferrer"&gt;Explore more technical articles&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://palks-studio.com/en/technical-notes" rel="noopener noreferrer"&gt;Read more technical notes&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://palks-studio.com" rel="noopener noreferrer"&gt;https://palks-studio.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>backend</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
