cpp2sqlite is a C++ tool that generates SQLite databases containing Swiss Healthcare Public Domain Drug Information. It aggregates data from multiple Swiss health authority sources (Swissmedic, BAG, Refdata, etc.) into SQLite databases used by AmiKo and other applications.
cd scripts && source steps_public1.source && ./download.sh
source steps_public2.source && ./download.sh
cd ../build && cmake .. && make -j9Note: BOOST_BIND_GLOBAL_PLACEHOLDERS is defined in CMakeLists.txt to suppress Boost bind placeholder deprecation warnings (the project doesn't use boost::bind directly, but Boost headers trigger the warning internally).
cpp2sqlite- Main tool, generates amiko_db SQLite databasespharma- Generates pharma.csvsai- Processes SAI (Refdata structured article information)zurrose- Processes Zur Rose data
All use BAG FHIR ndjson by default since 01.06.2026 (flagFHIR=true); pass --no-fhir to fall back to the legacy BAG XML. The --fhir flag still works as an explicit opt-in.
- Source:
src/bagFHIR.cpp/src/bagFHIR.hpp - Input:
downloads/fhir-sl-{de,fr,it}.ndjsonfrom https://epl.bag.admin.ch/static/fhir/foph-sl-export-latest-{de,fr,it}.ndjson (per-language NDJSON) - Each line is a FHIR Bundle with entries: MedicinalProductDefinition, RegulatedAuthorization, PackagedProductDefinition, Ingredient, ClinicalUseDefinition
- Prices (EFP/PP) and reimbursement data are in RegulatedAuthorization extensions under
reimbursementSL>productPrice - Legal status codes map to categories: A (756005022001), B (756005022003), C (756005022005), D (756005022007, 756005022008), E (756005022009)
- When
--fhiris active, all price lookups (including from Refdata and Swissmedic) must useBAGFHIR::getPricesAndFlags()instead ofBAG::getPricesAndFlags(), sinceBAG::parseXML()is not called and its prepList is empty
- Mandatory on SL prescriptions and invoices from 2026-07-01 (BAG Rundschreiben 2026-02-19); insurers may reject non-compliant invoices from 2027-01-01
- Source:
src/bagFHIR.cppcollectBundleIndC()— bundle-scoped pass over each NDJSON line (one Bundle per line) - Preferred source (BAG SL FHIR export >= v2.0.5): the explicit
indicationCodesub-extension carried on each limitation (RegulatedAuthorization.indication[].extension[regulatedAuthorization-limitation].extension[indicationCode].valueString, e.g.20403.01). The BAG changelog states the limitation code (ClinicalUseDefinition.id) and the indication code are independent fields, soXXXXX.NNmust NOT be reconstructed from the CUD id suffix. The limitation text is resolved via the siblinglimitationIndicationreference (e.g.ClinicalUseDefinition/CYRAMZA.01) into that CUD's text. Verified on the live DE feed: 718 / 6'775 bundles carry the explicit field. - Fallback (older feeds without the extension):
XXXXXfromRegulatedAuthorization.extension[reimbursementSL].extension[FOPHDossierNumber].valueIdentifier.value;.NNis the trailing all-digit segment of eachClinicalUseDefinition.id(e.g.CYRAMZA.01); onlytype == "indication"CUDs are picked up. The feed shipstypeas a plain string andindicationas a single object — both shapes are handled. - CUD text comes from
indication.diseaseSymptomProcedure.concept.text, with fallbackindication.extension[url == "limitationText"].valueString - Stored on
BAG::Preparation.indicationCodes(and copied onto everyPack) asIndicationCode {code, cudId, text} BAGFHIR::getIndCByRegnr(regnr, &codes, &text)returns the joined columns the schema expects: comma-joined codes + newline-joinedcode: textlines, deduped by code, in bundle order. Mirrors rust2xml ≥ 3.1.12 (INDIKATIONSCODE/INDIKATIONSCODE_TEXTXML elements). The function walks the entireprepListand merges IndicationCodes from every prep that shares the queried Swissmedic regnr, deduped by code — required because the same 5-digit Swissmedic regnr typically spans multiple FHIR bundles (only one of which carries the type=indication ClinicalUseDefinitions). An earlier early-return on the first matching prep with empty indicationCodes silently dropped IndC for ~80% of regnrs.- Schema/binding:
Sql::useIndCis set fromflagFHIRinopenDB(). When true, theamikodbschema gains two trailing TEXT columns (indikationscode,indikationscode_text) andinsertRowbinds positions 19/20. Non-FHIR builds emit the legacy 19-column schema, so apps reading by column index keep working. - iOS consumer:
generikacc/Generika/IndCSection.swift(issue zdavatz/generikacc#102) — guarded byAmikoDBManager isIndcColumnAvailableso older DB snapshots don't break the app
downloads/aips.xmlis only an index: eachMedicinalDocumentsBundlecarries the regnrs plus aDocumentReferenceURL. The text lives inAllHtml.zip(~1.1 GB, ~30'500 files), unpacked byscripts/download.shintodownloads/Refdata-AllHtml/.src/c2s/aips.cpp:246setsMed.contentHTMLPathonly if the html exists on disk, andmedList.push_back(Med)is guarded by!Med.contentHTMLPath.empty(). A missing html silently removes the whole medicine — noamikodbrow, hence no Swissmedic packages either, even thoughswissmedic_packages.xlsxandsai.dbhave them. The only trace is the "Missing html files" section ofoutput/amiko_report_*.html.- One bundle can list several regnrs (e.g.
61848+62069share6795ea24…-de.html), so one missing file can take several registrations with it. - Debugging recipe:
grep -o 'MedicinalDocuments/[0-9a-f]*-de\.html' downloads/aips.xml | sed 's|.*/||' | sort -uvs.ls downloads/Refdata-AllHtml | grep -- '-de\.html' | sort -u, thencomm -23. A healthy DE run has <10 missing out of ~10'200. - Incident 2026-08-19: the 04:02 run extracted 11'108 of 30'525 files — a full-length zip with a corrupt middle region (files present for offsets 0–19 MB and 705 MB–end, a 686 MB hole between).
unzip6.00 skipped those entries and still exited 0; the olddownload.shchecked neitherwget's norunzip's exit code and deleted the zip afterwards. Result: 6'514 of 10'232 DE documents missing,amiko_db_full_idx_de.dbat 1'694 rows, regnr 62069 gone. The server blob was fine and unchanged (same ETag/Last-Modified hours later), which made it look like a transport fault. It was not — see the root cause below. - Root cause (found 2026-08-20, same failure again):
wgetresumed across a server-side rewrite. Refdata publishesAllHtml.zipdaily at 04:00:37 CEST (Refdata.Articles.zipat 03:00:07) by overwriting the blob in place under the same URL — it is Azure Blob Storage. The cron job started at 04:00:01, so every download longer than ~36 s spanned their publish. On 2026-08-20 the first request gotContent-Length: 1119725848, the connection died at byte 704'608'900, andwget --tries=3retried by itself with aRangerequest; the206came back withContent-Length: 1118831176— a different, 894'672 bytes smaller file.wgetlogged both numbers, noticed nothing, and appended the tail of version B to the head of version A. It sends a bareRangewithoutIf-Range, so the server never gets to say "the entity changed". The archive is then exactly the announced length with a valid central directory and is garbage from the splice on: the first bad offset was file #19968 at 704'115'827, the byte where the connection had died. No exit code and no length check can see this — onlyunzip -tqq. - Two independent mitigations, keep both:
download.shnever resumes (each attemptrms the partial file and refetches the whole archive withwget --tries=1,ALLHTML_TRIESattempts, integrity test decides whether to retry), and the cron job moved from 04:00 to 05:00, out of the publish window. 05:00 rather than earlier: it then gets the blob written at 04:00 the same day, i.e. the freshest one. scripts/download.shnow stages the archive:wgetrc,unzip -tqq, an entry-count floor (ALLHTML_MIN_FILES, default 25000), extraction intoRefdata-AllHtml.new, and extracted-count vs. archive-entry-count. Any failure keeps the previous folder and exits 1. Do not "simplify" this back to a barewget && unzip— unzip exiting 0 is not evidence that it extracted anything.
- Consumed by
pharmaalone (src/pha/main.cpp:128->SWISSMEDIC2::parseXLXS), which reads column A (Zulassungsnummer), B (Dosisstärkenummer) and E (Zulassungsart), header in row 7, data from row 8. It never reachescpp2sqlite, so the AiPS databases are unaffected by it. - Swissmedic renamed the DAM node
erweiterte_ham.xlsx->erweiterte_ham_ind.xlsx("mit Indikation", in step withzugelassene_arzneimittel_ham->..._ham_ind). The old URL had been answering 404 since roughly Feb 2025 and nothing noticed:wget -Nleaves the previous file in place on a 404 and the caller ignored the exit code, sopharma.csvwas built from an 18-month-old list. Fixed 2026-08-20 — the step now downloads to a temp name, validates withunzip -t, and only then replaces the file. - The refresh changed 2'780 of 18'050
pharma.csvrows:export_product(the Zulassungsart) went from 1'309 empty / 87Exportzulassung/ 16'601Hauptzulassungto 6 empty / 0 / 17'991. Verified against the raw sheets that this is Swissmedic's own reclassification, not a parse regression — all 29 affected registrations are still in the list, 27 now purelyHauptzulassung. - A failure here is a warning, not an abort. The step runs in
steps_public1.source, whichamiko_datagates with|| die; making it fatal would let a Swissmedic outage block the whole AiPS build over an input AmiKo never reads. The warning names the date of the stale file so cron mail shows how old it is. Only a missing URL and no previous copy is fatal. - Sibling lists on the same page, in case one of these breaks the same way:
zugelassene_packungen_human.xlsx(that one isswissmedic_packages.xlsx, needed by AmiKo and already staged/validated),zugelassene_arzneimittel_ham_ind.xlsx,erweiterte-ham-ohne-indikation.xlsx. Index page: https://www.swissmedic.ch/swissmedic/de/home/services/listen_neu.html
scripts/amiko_data+scripts/amiko_lib.share the gated version of the AiPS cron pipeline. They replace nine hand-written scripts in/usr/local/src, one per build (aips, fachinfo de, fachinfo fr, sai, pinfo) and one per publishing target, each calling the next.- Four stages, run in order by default, individually selectable:
aips(download +cpp2sqlitede/fr),fachinfo(fachinfo_aifrequency dbs),sai(sai+nonpharma),pinfo(cpp2sqlite --pinfo). One cron entry instead of five; the order is what removes the races, so do not split them back onto fixed clock times. - Hosts, addresses and directories are not in the repository. They live in
scripts/amiko_targets.conf, which is gitignored;amiko_targets.conf.exampledocuments the format. The conf setsAMIKO_WWW(local web root),AMIKO_AI(fachinfo_ai checkout) andAMIKO_REMOTES(user@host|dir|portentries). Every value honours an environment override, so a test run can redirect the whole pipeline. A missing conf aborts before anything runs. - What the old layout got wrong, and why each guard exists:
- No exit code was ever checked.
download.shrefuses a badAllHtml.zipand exits 1, but the caller built anyway. Every stage is now gated by|| die. - Nothing looked at the databases. On 2026-08-19 a 1'694-row
amiko_db_full_idx_de.db(instead of 4'640) went to four servers.verify_outputsenforces mtime (must be written by this run), a byte floor, a shrink guard against the published copy,PRAGMA integrity_checkand a row floor on the named table. - Registration canaries.
canary_regnrchecks that specific regnrs are present, because a missing content html silently drops a whole medicine without changing anything structural.CANARY_REGNRSstarts with 62069, the casualty of that incident. rm *.zip. Three of the old publishing scripts began by deleting every archive inoutput/, so the 05:00 sai run wiped the 04:00 amiko archives and the 07:00 copy to the web root then copied files that no longer existed.zip_outputremoves only the archive it is about to write (zipupdates an existing archive in place, so it does have to be removed).- Fixed-offset race. The fachinfo scripts ran at 04:30 while the de/fr build needs ~35 min, with no interlock, so they could copy a database that was still being written.
- Non-atomic publishing. A bare
scpof a 400 MB database over the live one lets a client fetch a half-written file.publish_localandpublish_remoteboth copy to a temp name and rename.
- No exit code was ever checked.
- An unreachable target is a warning, not an abort: the remaining targets still get the build and the run exits 1 so cron mails about it. This matters — the old fachinfo scripts ran under
set -ewith a long-dead host first in their list, so every run aborted before reaching the two hosts that did work, and one of them servedamiko_frequency_*.dbfrom 2026-07-16 for a month. That entry is commented out in the conf; do not leave dead hosts in the list, a warning on every run trains everyone to ignore it. - One of the remote targets also pulls some files by itself with
wget. Do not read a fresh file there as proof that the push worked — that is exactly what masked the stale month. - Downloading happens once, in
stage_aips;fachinfo,saiandpinfobuild from the sameinput//downloads/.pinfotherefore never refetches, and it does not run at all if the download was rejected — unlike the old 07:00 job, which built regardless. - Cron PATH is minimal (
/usr/bin:/bin).sqlite3is checked at load time and a miss is fatal:verify_outputsused to print "not checked" and carry on, i.e. a run with no gate that still reports success and publishes.cargois not on that PATH either, so thefachinfostage sources~/.cargo/env. The targets file is resolved with${BASH_SOURCE[0]%/*}rather thandirname, for the same reason. - Marker files (
amiko_update_done,sai_non_pharma_done) are written last, after every file they refer to is in place; clients poll them. - Knobs:
AMIKO_SKIP_DOWNLOAD=1,AMIKO_SKIP_BUILD=1(verify and publish what is already inoutput/),AMIKO_NO_PUBLISH=1,AMIKO_NO_REMOTE=1,AMIKO_SHRINK_PCT,AMIKO_SRC,AMIKO_CONF. amiko_lib.shis deliberately not shared withso_data_lib.sh: the two pipelines publish different files to different places, and keeping them apart means a change here cannot break the other one.download.shunzips SwissPedDose andtransfer.zipwith-o. Without itunzipprompts when the extracted file already exists, which is why the old download wrapperrmed those two files first; under cron a prompt is a hang, not a question.
- Feed:
artikel_vollstamm_zurrose.csvandartikel_stamm_zurrose.csvare downloaded viascripts/download_zr.sh(SFTP from ftp.zur-rose.ch). - Schema change (2026-05): a trailing column
Exfact(V, index 21) was appended, growing the row from 21 to 22 semicolon-separated fields. All four column-count guards must match the new width —src/zur/stamm.cpp:69,src/zur/stamm.cpp:129,src/zur/voll.cpp:152,src/zur/voll.cpp:355. Note thatvoll.cpp::parseCSV(line 152) silentlycontinues on mismatch instead of exiting, so a missed bump there empties the output DB without an error message. Exfactis the Zur Rose ex-factory price and is wired intorosedb.exfpriceas a fallback for the BAG EFP involl.cpp::parseCSV(~line 304). BAG values stay canonical for SL-listed drugs; Zur Rose Exfact fills the long tail. In--fhirbuilds without BAG XML this liftsrosedb.exfpricepopulation from 0/163858 to 163858/163858.
- Hosts and directories are not in the repository.
ZR_WWW(publish target) andZR_DOMAIN(sftp server) live inscripts/zr_targets.conf, gitignored, withzr_targets.conf.exampleas the template; credentials stay inscripts/passwords, also gitignored.so_data_lib.shanddownload_zr.shboth abort if the conf is missing. Note the deployment order:so_data/so_data_fullare already in cron on the publishing host, so agit pullthere without the conf stops them — create the conf first, then pull. Nothing on that host pulls cpp2sqlite automatically (its 01:30 job pullsaips2sqlite, a different repo), so a push alone changes nothing there. - Every feed is fetched into
input/zurrose/.staging.<pid>(per run, so overlapping cron jobs cannot wipe each other's half-fetched files; anEXITtrap removes it, plus a sweep for anything older than 4 h), validated, and only then moved over the live file; the previous version is kept ininput/zurrose/.bak. A rejected feed leaves the last known-good input in place and the script exits 1 — callers must check that before running./zurrose. - Checks per file: sftp exit code, remote-vs-local byte count, size/line floors, shrink guard vs. the current file (
ZR_SHRINK_PCT, default 70%), no NUL bytes, no HTML error page, encoding stability, and the;-column count each parser insrc/zur/requires. The spec table at the top of the script carries those numbers with thesrc/zur/*.cppline that enforces them — when a feed layout changes, bump the parser guard and the table, otherwise the download is rejected. - Three feeds ship as ISO-8859-1 and are consumed as-is (
artikel_stamm_zurrose.csv,Autogenerika.csv,medix_kunden.csv); onlyArtikelstamm_Vollstamm.csvandkunden_alle_dynamics_ce.csvare converted to UTF-8. The validator fails on an encoding flip rather than demanding UTF-8 everywhere. - Transport is
sftp -oBatchMode=no -b <batchfile>, notscp: against the Serv-U server at Zur Rose, OpenSSH 9+ scp reportsExit status -1and exits 1 even when the transfer succeeded, so its exit code cannot gate anything. sftp also reports the remote size (ls -l), which is what the transfer-completeness check compares against. - One session for all 11 files, not one per file. The server rate-limits new connections: with a session per file, two overlapping runs opened dozens between them and got refused with
rc=255partway through (measured: 4 of 11 feeds lost). Batch lines are prefixed with-so a missing file does not abort the rest; each file is then verified locally and only the incomplete ones are retried (3 rounds). A full run is ~7 s and 2 connections, and two concurrent runs both complete 11/11. - A
sftp_pingpreflight (pwdin its own session) runs before anything is staged. If the server is unreachable or the login fails, the script exits 2 having touched nothing —so_datatreats that as "skip this cycle" and exits 0,so_data_fulltreats it as an error. Exit 1 still means "connected, but at least one feed was rejected". ZR_CHECK_ONLY=1validates the current inputs without downloading;ZR_DRY_RUN=1downloads and validates without replacing anything;ZR_FORCE=1promotes anyway.scripts/so_data_fullandscripts/so_dataare the gated versions of the two so.zurrose.ch cron jobs — full (download.sh+download_zr.sh, fulldb + atcdb) and quick (download_zr.sh,--zurrose=quick). Both abort if a feed was rejected or a build failed, verify the generated files (sqliteintegrity_check,rosedbrow counts, JSON well-formedness, line counts, size floors, shrink guard vs. what is published, and mtime — a file not rewritten by this run is refused), then install them into/var/www/so.zurrose.ch/roseatomically (copy to a temp name, then rename). The checks live inscripts/so_data_lib.sh, which both source; each script only carries its own list of output files and their floors.- Canaries (
canary_db/canary_csv/canary_json_keyin the lib): a build can pass every structural check and still answerhttps://so.zurrose.ch/smart/fullwith nothing, because the specific article or customer asked about is missing. Both scripts therefore check a real basket (CANARY_PHARMACODES) againstrosedb/rose_stock.csvand a real customer (CANARY_GLNCODES) againstrose_ids.jsonandrose_conditions_new.jsonbefore publishing. Edit those arrays at the top ofso_data/so_data_fullwhen the sample order stops being representative. --zurrose=quickstill regeneratesrose_conditions_new.json,rose_ids.json,rose_autogenerika.json,rose_direct_subst.json,rose_nota.jsonandGrippeimpfstoff.json(those run unconditionally insrc/zur/main.cpp), plusrose_stock.csvwhich is quick-only.Grippeimpfstoff.jsonis legitimately[\n](5 bytes) out of flu season, hence its 2-byte floor.
VOLL::closeDB() calls sqlite3_finalize(statement) then sqlite3_close(db), so it must be called exactly once. Calling it twice double-frees both handles and corrupts the glibc heap — the failure surfaces as free(): invalid next size (fast) on process exit and, depending on arena state, may not reproduce on smaller workloads (atcdb hid it while fulldb crashed every run). src/zur/main.cpp previously had a stray second closeDB block right after the fulldb/atcdb branch; the single call inside the branch is the correct lifecycle.
src/c2s/ean13/holdsfunctii.cpp+functii.has ordinary files, copied fromywesee/BarcodeGeneratoratc1f17da(the<cstdint>fix). Edit them like any other source; one commit, no pointer bump, nogit submodule update.- It used to be a submodule, and the three-step dance was a real hazard: a
git pullwithoutgit submodule updateleft the working tree on the 2019 commit7516528, which silently removed the<cstdint>include again.git statusthen shows a submodule "change" that is actually a revert — committing it would have broken the Funtoo build server. - Only the library is vendored; upstream's standalone demo (
main.cpp,sample-input.txt,sample.html) was dropped. Changes here do NOT flow back toywesee/BarcodeGenerator— port by hand if that repo needs them. - Single consumer:
EAN13::createSvg()ingetBarcodesFromGtins()(src/c2s/cpp2sqlite.cpp:156), which embeds one SVG barcode per package GTIN into the Fachinfo html.CMakeLists.txt:50and:71list the two files directly.
cpp2sqlite --lang de used to take 463 s; it now takes 151 s (3.07x), single-threaded, with byte-identical output. The profile that drove this: 45.5% of the medicine loop was package lookups, 53.6% was HTML processing, and sqlDb.insertRow was 1.9 s — 0.47%. Writing the database is not the bottleneck and never was.
- Never scan a list to find a record.
swissmedic.cpp,refdata.cpp,bagFHIR.cppandbag.cppeach buildunordered_mapindexes at the end of their parse function (rowsByRegnr/rowByGtin/rowByGtin12,byGtin5/byGtin13/byRegnr5,prepsByRegnr/packByGtin). These are only safe because the containers are filled during parsing and never touched afterwards — if you ever append toprepList/artList/theWholeSpreadSheetlater, the indexes go stale. - The indexes use
emplace, deliberately: most of the old scansbreakon the first hit, so the first entry must win.operator[]would keep the last one and silently change prices, categories and names. - Regnr indexes store ascending positions, so
getNames/getAdditionalNamesstill append packages in list order — that order reaches thepackagescolumn. getPricesAndFlags()(bothbag.cppandbagFHIR.cpp) lazily fillspackMap, whichgetPackageFieldsByGtin()reads afterwards. The index lookup keeps that side effect. Itsgoto prepareResultis gone: with a map there are no nested loops left to break out of.- Range-for over these lists must use
const&.for (Article art : artList)andfor (BAG::Preparation prep : prepList)copied a struct full ofstd::strings — the latter dragging its wholepacksvector — on every iteration. Measured: Swissmedic's indexed scan did 97.4 M iterations in 25.9 s while BAG's copying scan did 36.6 M in 71.1 s, i.e. 7x the cost per element purely from copying. BEAUTY::cleanupForNonHtmlUsage()is one pass with a hash lookup on each&...;, not 76 consecutiveboost::replace_allcalls over an ~85 KB document. Equivalent because no replacement output contains&(so passes cannot cascade) and every key runs from&to its first;(so at most one key can match at a position). Add new entities toentityTable();maxEntityLength()adapts automatically.- The two
std::regexinREFDATA::getArticleDocument()are nowfixLeadingDiv()/fixHtmlTagQuotes().std::regexcost 288 s across the 29'693 downloaded html files versus 1.2 s for plain string ops.fixHtmlTagQuotesmust reproduce greedy\S*, i.e. match the last">"inside the run of non-space characters after<html, and it needs separate search and copy cursors — conflating them corrupted 11 files in the first attempt. fixLeadingDivmatches in 0 of 29'693 files, so that path is unexercised by current data.
bag.cpp / bagFHIR.cpp are shared — a change there is not a cpp2sqlite change. CMakeLists.txt links them into cpp2sqlite, zurrose, pharma and sai (beautify.cpp additionally into cpp2sqlite and pharma, refdata.cpp/swissmedic.cpp only into cpp2sqlite). Verifying cpp2sqlite alone proves nothing about the other three: rosedb.exfprice, pharma.csv prices and sai.db all come through getPricesAndFlags().
Careful with --fhir outside cpp2sqlite: zurrose has it inverted. src/zur/main.cpp:199 does bool flagFHIR = false; if (!vm.count("fhir")) flagFHIR = true;, so for zurrose omitting the flag selects FHIR and passing --fhir selects the legacy BAG XML. pharma (src/pha/main.cpp:107) and sai (src/sai/main.cpp:205) use the normal polarity (if (vm.count("fhir")) flagFHIR = true;) — passing --fhir there selects FHIR, and omitting it selects the legacy BAG XML. cpp2sqlite defaults to FHIR and opts out with --no-fhir. So to exercise bag.cpp you pass --fhir to zurrose, --no-fhir to cpp2sqlite, and nothing to pharma and sai.
Verifying a change here: build both versions and compare the generated artifacts, not just row counts. For sqlite, hash every table, column and row; normalise the build timestamp in the html footer (Auto-generated by ... on <date>) and, for the *_report.html files, the run timestamp and the paths echoed from argv. Two runs of unchanged code then hash identically, so any real difference shows up. Build the "before" side from the previous commit in a git worktree rather than trusting whatever binary happens to sit in build/ — it may be months older than HEAD.
The matrix that covers every code path these files touch, 16 configurations:
cpp2sqlite:--lang de,--lang fr,--pinfo,--no-fhir(19-column legacy schema),--without-sappinfozurrose:--zurrose=fulldb/atcdb/quick, each with and without--fhirpharma: default,--fhir,--storagesai: default,--fhir
The 3x commit (371cec4) was verified this way: identical amikodb, rose_db_new_full.db, rose_db_new_atc_only.db, rose_stock.csv, all six zurrose JSON outputs, pharma.csv and sai.db.
GCC 13+ (and current libstdc++) no longer pulls many standard headers transitively. New code that uses standard types/streams must #include them explicitly or the Funtoo build server breaks:
<cstdint>foruint8_t/int32_t/ etc. (trippedsrc/c2s/ean13/functii.cpp— fixed in ywesee/BarcodeGenerator c1f17da)<fstream>forstd::ofstream/std::ifstream(trippedsrc/sap/main.cpp,src/dru/main.cpp— fixed in 0203368, plusbagFHIR.cpp,c2s/peddose.cpp,c2s/sappinfo.cpp,c2s/refdata.cppcleaned up preemptively)
If a new source file uses these (or <sstream>, <iomanip>, etc.) without an explicit include, it will compile locally on older toolchains and break on the build server.
src/- C++ source filessrc/c2s/- cpp2sqlite specific sourcessrc/pha/- pharma specific sourcessrc/sai/- SAI specific sourcessrc/zur/- zurrose specific sourcesscripts/- Build and download scriptsinput/- Input data files (not in git)downloads/- Downloaded source files (not in git)output/- Generated databases and reports (not in git)