Mitä uutta?

Versiohistoria ja palvelun kehitys · Nykyinen versio: v1.24.3

Tälle sivulle on koottu palvelun versiohistoria, uudet ominaisuudet ja keskeiset parannukset.

v1.24.3

Automaattitestit ajetaan kerran: vain pull requestissa, ei enää uudelleen mainiin pushattaessa.

  • Push-ajo pois tests.yml:stä ja docs-check.yml:stä. PR-ajo testaa jo merge-refin eli yhdistetyn tuloksen, joten mergen jälkeinen main-push-ajo testasi saman koodin toiseen kertaan. 1.–22.9.2026 main-push-ajoja oli 47, joista 41 PR-mergejä ja loput käsin pushattuja dokumenttipäivityksiä, yhteensä 47 Actions-minuuttia. Viikkoajon commit ei käynnistänyt push-ajoa muutenkaan (H21). Mainin voi yhä testata käsin (workflow_dispatch, käytössä molemmissa työnkuluissa).
  • Sivustoon ja uutiskirjeeseen muutos ei vaikuta.
v1.24.2

Tietosuojalinkki jokaisen sivun alatunnisteeseen ja tiivis tietosuojaseloste.

  • Löydettävyys. Jokaisen sivun alatunnisteessa on linkki Tietosuoja, joka vie Tietoja-sivun Tietosuoja-osioon: etusivu, Tietoja, Tilaa, Mitä uutta?, 404-sivu, tilausfunktioiden sivut ja koostesivut. Julkaistuihin koosteisiin lisättiin vain tämä linkki; niitä ei renderöity uudelleen (päätös 9), joten niiden versio, fontit ja aikaleimat säilyvät. Tilauslomakkeessa on linkki selosteeseen keräyshetkellä.
  • Seloste yhteen paikkaan. Tietosuojatiedot olivat kahdessa paikassa: rekisterinpitäjä Tietoja-sivulla, muu Tilaa-sivulla. Seloste on nyt Tietoja-sivun Tietosuoja-osio, ja Tilaa-sivulla on lyhyt ilmoitus rekisterinpitäjästä ja tarkoituksesta sekä linkki selosteeseen. Omistajan linjauksen (22.9.2026) mukaan seloste on tiivis eikä kerro teknisiä yksityiskohtia.
  • Korjatut väitteet. Seloste sanoi palvelun keräävän ainoastaan sähköpostiosoitteen, mutta myös erikoisalavalinnat tallennetaan tilaajalistaan, ja lähetyslokiin kirjataan, kenelle kooste on lähetetty. Säilytysajaksi luvattiin "kunnes peruutat tilauksen", mutta peruutus merkitsee osoitteen postituspalvelussa peruutetuksi eikä poista sitä. Vastaanottajista puuttuivat sivuston isännöinti (Vercel) ja viikkoajo (GitHub). Tilaa-sivun "ennen sen avaamista osoitettasi ei tallenneta mihinkään" on nyt "ei lisätä tilaajalistalle", koska vahvistusviesti lähtee postituspalvelun kautta. Lisäksi seloste kertoo nyt EU:n ulkopuoliset siirrot, rekisteröidyn oikeudet ja valitusoikeuden, jotka siitä puuttuivat.
v1.24.1

Tietosuojaosio nimeää rekisterinpitäjän.

  • Tilaa-sivun Tietosuoja-osio sanoi rekisterinpitäjäksi "sivuston ylläpitäjä (yhteystiedot Tietoja-sivulla)". Viittaus osoitti tyhjään: site/tietoja.html ei sisältänyt yhteystietoja eikä sähköpostiosoitetta lainkaan. Rekisterinpitäjä on nyt nimetty — CCode, y-tunnus 3649639-4 — ja yhteysosoitteena on uutiskirjeen lähettäjäosoite uutiskirje@pediatrics.fi. Sama tieto lisättiin Tietoja-sivulle omaksi osiokseen, joten viittaus pitää nyt paikkansa molempiin suuntiin. Pelkkä nimi ei yksilöi yhtiötä, joten y-tunnus on mukana (GDPR 13 art. 1 a).
v1.24.0

Toiminnallisuuskierros: idempotenssi, valvonnan havaintokynnys ja yksi totuuslähde erikoisaloille.

  • Pelastusajo ei enää postita uudelleen jo palvelluille (A1). save_sent tiivisti listan, joka oli jo tiivistetty: main() kutsuu sitä muodossa already + sent, jossa already tulee lokista. Vanhat rivit muuttuivat tiivisteen tiivisteiksi, eikä choose_recipients enää tunnistanut heitä — toinen ajo samalle päivälle olisi lähettänyt kirjeen kahdesti. Reprodusoitu ja korjattu (log_key on idempotentti, hash_email ei). Vika syntyi v1.22.0:ssa eikä ehtinyt laueta: v1.22.0:n jälkeen ei ole tehty pelastusajoa, ja 13.9. lokin rivit vastaavat yhä message_ids-avaimia.
  • Puuttuva kirje on havainto, ei varoitus (A2, H34). Jos kirje meni kolmelle neljästä, lähetin palautti 0, viikkoajo nieli koodin ja tuoreusvalvonta tulosti varoituksen, jota workflow ei edes annotoinut. Nyt tarkistus erottaa lähetyksen jälkeen liittyneen (lokin queued_at vs. kontaktin luontihetki) aidosti palvelematta jääneestä, ja jälkimmäinen laukaisee saman hälytyksen kuin puuttuva kooste. Tuntematon liittymishetki lasketaan havainnoksi: väärä hälytys maksaa yhden viestin, väärä hiljaisuus viikon kirjeen.
  • Uudelleentilaajan tuoreet valinnat tallentuvat (A3). Peruuttanut, joka tilasi uudelleen eri erikoisaloilla, sai vanhan valintansa mukaisen kirjeen, vaikka vahvistussivu kertoi tilauksen onnistuneen. Vahvistuslinkin toistosuoja (AT9) koskee nyt vain aktiivista tilaajaa — peruuttaneen uudelleentilaus on tuore, allekirjoitettu suostumus juuri niillä aloilla, jotka hän valitsi. Tämä kumoaa aiemman päätöksen, jonka mukaan kategorioita ei ylikirjoiteta missään tilanteessa.
  • Viikkoajon idempotenssia vartioidaan suorittamalla (A4). Kaksi vartijaa tarkisti vain, että tietyt merkkijonot ovat tiedostossa. Todiste: injektio, joka teki koko lohkosta kuollutta koodia merkkijonoja koskematta, meni vanhoista testeistä läpi. Kuusi uutta testiä poimii lohkon ja ajaa sen telineessä (sama tekniikka kuin tuoreusvalvonnan askeleella): pelastusajo, käsitelty kirje, yksi lippu, kaksi lippua, uusi päivä ja rikkinäinen manifesti.
  • Rikkinäinen manifesti pysäyttää ajon (A7). Lukuvirhe meni samaan haaraan kuin "päivää ei ole julkaistu", joten se olisi johtanut jo julkaistun koosteen uudelleengenerointiin — sama vikaluokka kuin 30.8.2026. Nyt lukuvirhe on oma tilansa ja vaatii FORCE_REGENERATE=1.
  • Yksi totuuslähde erikoisaloille (A5). Suodatus on aina toiminut koosteen ###-otsikoista. Mallin kirjoittamaa [kategoria: X] -leimaa ei lukenut renderöinnin jälkeen mikään, ja fetch_articles.py:n MeSH-luokittelijan tulosta ei lukenut yksikään moduuli. Molemmat poistettu; config/categories.json jää taksonomiaksi, ja uusi vartija kaatuu, jos kehotteen otsikot, taksonomia ja suodattimen tunnistus eriytyvät. Tunnistamaton otsikko menee yhä kaikille (epäonnistuu auki), joten vartija on se mekanismi, joka pitää personoinnin elossa.
  • tekoälymallin uusinnat mahtuvat työn aikakattoon (A6). Neljä 600 sekunnin kutsua teki pahimmasta tapauksesta ~40 min, kun workflow'n katto on 30 min: runner olisi tappanut työn kesken uusinnan, jolloin bashin EXIT-käsittelijä ei suoritu eikä /fail-pingiä lähde. Aikakatto on nyt 300 s ja testi vartioi suhteen.
  • Todennus. 299 testiä läpi; kuusi vikainjektiota, joista jokainen kaatoi oman vartijansa.
v1.23.1

Lähdeluettelon otsikot merkitään englanninkielisiksi.

  • Koostesivun lähdeluettelossa on PubMedin englanninkieliset otsikot suomenkielisen sivun sisällä. Ilman lang="en"-merkintää ruudunlukija lausuu ne suomen ääntämyssäännöillä (WCAG 3.1.2, osan kieli). Lehden nimi, vuosi ja kirjoittajat jäävät merkitsemättä: ne ovat erisnimiä. Näkyy seuraavasta viikkoajosta alkaen — vanhoja koosteita ei renderöidä uudelleen (päätös 9).
v1.23.0

Käytettävyyskierroksen korjaukset: kontrastit, 404-sivu, tagitettu PDF ja selkeämmät virhepolut.

  • Erottimen kontrasti (A3). Metatietojen · oli 1,35–1,66:1 jokaisella sivulla molemmissa teemoissa — vaatimus on 4,5:1. Se on nyt samaa sävyä kuin ympäröivä metateksti (5,3:1). Merkin ympärille lisättiin myös oikea välilyönti, joten ruudunlukija ei enää lue "sunnuntaisin·PDF" yhtenä sanana.
  • Sivuston tunnus luettavissa (A9). Tunnuksen osien väli oli pelkkä marginaali, joten linkin nimi oli "Lastentauditviikkokooste". Väli on nyt merkkauksessa.
  • Oma 404-sivu (A4). Rikkinäinen linkki vei Vercelin englanninkieliselle tekstisivulle, jolla ei ollut navigaatiota eikä paluupolkua. Uusi sivu on sivuston näköinen ja tarjoaa neljä reittiä eteenpäin.
  • PDF on tagitettu (A5). pdf_variant="pdf/ua-1" tuottaa rakennepuun (otsikot, kappaleet, listat, linkit), /MarkInfo-merkinnän ja DisplayDocTitlen. Ruudunlukija ei enää arvaa lukujärjestystä. Mitattu: teksti ja sivumäärä ennallaan, koko +24 kt.
  • Tulostus tummasta tilasta (A11). @media print unohti kaksi muuttujaa, joten tummaa tilaa käyttävän tulosteessa painikkeen teksti oli 2,78:1 ja osiotunniste 2,63:1 valkoisella paperilla.
  • Tilausfunktioiden sivut vastaavat sivustoa (A6). Vahvistus-, peruutus- ja asetussivuilta puuttui hyppylinkki ja koko alatunniste; ne olivat umpikujia.
  • Virhepolku ei hukkaa lomaketta (A7). "Valitse vähintään yksi erikoisala" neuvoi linkillä tyhjälle tilaussivulle, jolloin osoite ja 13 valintaa piti syöttää uudelleen. Nyt neuvo on paluupainike, kuten asetussivulla jo oli. Nopeusrajoituksen sivu kertoo myös jäähyn pituuden (A17).
  • Kuittaus vastaa valintaa (A12), arkiston yhdeksän "PDF"-linkkiä sai erottuvan nimen (A10), uutiskirjeen runkoon tuli kielimääre (A16), ja hunajapurkin hylkäys kirjataan maskattuna lokiin (A14) — aiemmin väärä positiivinen oli näkymätön sekä käyttäjälle että ylläpitäjälle.
  • Navigaatio kestää kirjainvälien kasvatuksen (A15): 320 px:n näytöllä se vei aiemmin 335 px (WCAG 1.4.12); nyt se rivittyy.
  • Todennus. axe-core: 0 rikkomusta kuudella sivulla molemmissa teemoissa (aiemmat "incomplete"-solmut mitattiin käsin — ne olivat juuri ne, jotka alittivat rajan). Kontrasti mitattuna selaimesta, tekstivälit ja kohdekoot mitattuina, neljä vikainjektiota.
v1.22.2

Kategorialeiman poisto ei enää jätä koodijaksoa koosteeseen.

  • Mikä näkyi lukijalle. Tiivistin merkitsee jokaisen artikkelin erikoisalan leimalla, joka poistetaan ennen julkaisua. Kun leima oli kirjoitettu koodijaksoon (`[kategoria: x]`), poisto vei vain hakasulkeisen osan ja riville jäi tyhjä koodijakso. Se näkyi koostesivulla, sivuston PDF:ssä ja tilaajan liitteessä: kuudessa kymmenestä koosteesta, 33–63 kappaletta koostetta kohden (19.7.–13.9.2026). Korjaus tekee ympäröivistä backtickeistä osan kuviota.
  • Etusivun nostokortti kertoo taas sisällöstä. Nostojen laskenta vaati rivin päättyvän lihavointiin, mutta rivi päättyi leimaan — tulos oli 0 jokaisessa kymmenessä koosteessa, ja kuvaus putosi varapolulle "N artikkelia.". Se toisti kortin yläpuolella olevan artikkelimäärän eikä kertonut koosteesta mitään. Laskenta poistaa leimat ensin, jolloin teksti on jälleen "N artikkelista tehtiin M nostoa.".
  • Kolme vartijaa, kaksi injektiota. Yksi testi väittää nimenomaan backtickeistä (aiempi väite "[kategoria: ei esiinny" meni läpi koko ajan), yksi ajaa renderöintiketjun läpi ja yksi laskee nostot leimatuilta riveiltä. Vanhan kuvion palautus kaataa kaikki kolme.
  • Jo julkaistuja koosteita ei renderöidä uudelleen (päätös 9): niiden versioleima, fontit ja tuotantoaikaleima säilyvät. Korjaus näkyy seuraavasta viikkoajosta alkaen.
v1.22.1

Mallinimet pois julkaistulta sivulta; tuoreusvalvonnan kattavuustarkistus korjattu.

  • Suodatus renderöinnissä (päätös 4). "Mitä uutta?" -sivu generoidaan tästä muutoslokista jokaisessa ajossa, joten sivulta oli luettavissa tiivistimen mallinimi ja mallitunnus. Renderöinti korvaa ne nyt neutraalilla ilmauksella; muutosloki itse säilyy tarkkana. Päätös tehtiin 3.9.2026, mutta korjaus jäi erän 4 rajauksen ulkopuolelle, joten nimi oli tuotannossa v1.22.0 asti.
  • Vartija koko site/-puulle. Testi kaatuu, jos mallinimi esiintyy missään julkaistavassa tekstitiedostossa. Ilman sitä käsin siivottu sivu palautuisi ennalleen seuraavassa viikkoajossa — sivu ei ole lähde vaan tuloste. Suodattimessa on lisäksi varapolku, joka nappaa myös nimen, jota ei ole vielä kirjoitettu; todennettu vikainjektiolla molempiin suuntiin.
  • Tuoreusvalvonnan kattavuustarkistus vertaa jälleen oikeita arvoja. v1.22.0 tiivisti lähetyslokin osoitteet, mutta check_freshness.py jäi vertaamaan Resendin selkokielistä tilaajalistaa tiivisteisiin. Yksikään vertailu ei osunut, joten tarkistus ilmoitti joka viikko kaikki tilaajat puuttuviksi — 14.9.2026 lokissa luki "7/7 tilaajaa ei ole lähetyslokissa", vaikka kirje oli lähtenyt kuudelle. Aitoa osittaista lähetysvirhettä ei olisi enää erottanut mikään. hash_email ja log_key siirrettiin api/_common.py:hyn, jotta lokin kirjoittaja ja sen lukija käyttävät samaa muotoa; vanhat selkokieliset lokit kelpaavat yhä. Kolme vartijaa, yksi vikainjektio.
  • Kuollut koodi poistettu. get_latest_digest_pdf oli merkitty käyttämättömäksi (tervetuloviestiä ei kytketty päälle, päätös c) ja säilytetty vain omia testejään varten. Sen takia jokaiseen Vercel-funktioon niputettiin site/manifest.json ja kaikki koosteiden PDF:t; vercel.json sisältää nyt vain api/**. Samalla poistui render.extract_category_tags, jonka dokumentaatio väitti sitä käytettävän personoinnissa, vaikka tuotannossa sillä ei ollut yhtään kutsujaa. Neljä testiä poistui mukana; vahvistussivun AT8-vartija säilyi ilman kuollutta puoliskoaan.
  • Muutosloki v1.22.0 korjattiin väittämästään. Se sanoi, ettei check_freshness.py muutu — kenttä oli kuitenkin sillä kaksi lukijaa, ja toinen jäi päivittämättä. Formaattimuutoksessa lukijat on laskettava, ei arvattava.
v1.22.0

Tilaajien osoitteet pois lähetyslokista (tiivisteinä).

  • Mikä muuttui. data/<pvm>/newsletter_sent.json kirjaa vastaanottajat nyt muodossa sha256:<hex> selkokielisen osoitteen sijaan, ja sama koskee message_ids-kentän avaimia. Viisi olemassa olevaa lokia on muunnettu; lukumäärät säilyivät ennallaan.
  • Miksi. Loki committoidaan jokaisen ajon mukana, joten se on ainoa paikka jossa tilaajalista päätyy versionhallintaan. Moduulin dokumentaatio on luvannut §6:ssa jo pitkään, ettei osoitteita lokiteta selväkielisenä — mask_email toteutti sen stdout-tulosteessa, mutta tiedosto itse jäi ulkopuolelle.
  • Idempotenssi säilyy molempiin suuntiin. sent-listaa käytetään vain jäsenyystestiin, eikä osoitetta lueta siitä koskaan takaisin. choose_recipients hyväksyy sekä vanhan selkokielisen että uuden tiivistetyn muodon, joten formaatin vaihto ei postita jo palveltuja uudelleen. check_freshness.py lukee vain listan pituuden eikä muutu.
  • Kaksi vartijaa. Yksi kaatuu jos lokiin päätyy @-merkkinen osoite — se olisi mennyt läpi, jos vain sent olisi tiivistetty ja message_ids jäänyt avaimennetuksi osoitteella. Toinen kaatuu jos vanha selkokielinen loki lakkaa täsmäämästä.
  • Mitä tämä EI korjaa. Osoitteet ovat yhä git-historiassa. Niiden poistaminen vaatisi historian uudelleenkirjoituksen, joka on erillinen omistajan päätös.
v1.21.0

Ajastuspuskuri kasvatettu: cron 13 14 * * 013 9 * * 0 (omistajan päätös 7.9.2026).

  • Miksi. Vanha puskuri oli 2 h 47 min, ja mitattu ajautuma ylitti sen: 6.9.2026 ajo käynnistyi +2 h 48 min myöhässä eli klo 20:00 Suomen aikaa, scheduled_send_at palautti None ja kirje lähti heti klo 20:04 kuudelle oikealle tilaajalle. Neljä seitsemästä ajosta oli ylittänyt puskurin — klo 20:00 oli säilynyt sattumalta, ei rakenteen ansiosta.
  • Uusi puskuri on 7 h 47 min (kesä) / 8 h 47 min (talvi), eli noin kaksi kertaa pahin mitattu ajautuma (3 h 50 min) ja yhä yli sisarrepon pahimman (5 h 10 min).
  • Miksi siirto on turvallinen. Katsauksen päivä ja viikkonumero tulevat ajopäivästä (run_weekly.sh: DATE=$(date +%F), ajuri UTC:ssä), ja sunnuntai pysyy sunnuntaina molemmilla kellonajoilla — aikaisempi ajo itse asiassa loitontaa maanantain rajasta. Artikkeliaukkoa ei synny: reldate_days: 7 + reldate_overlap_days: 1 + PMID-dedup kattavat siirron (ks. config/journals.json _dedup_note).
  • Varapolku säilyy. scheduled_send_atin None-paluu on yhä invariantti: ajastimen viivettä ei ole ylhäältä rajattu, joten puskuria ei voi mitoittaa niin isoksi että varapolun voisi poistaa.
  • Vartija cronin ja dokumentaation erkaantumiselle. Uusi testi vaatii, että weekly-digest.yml:n cron esiintyy sellaisenaan AGENTS.md:ssä ja ARCHITECTURE.md:ssä ja että ihmisluettava kellonaika vastaa sitä. Tämä on sama vikaluokka kuin H22: väärä luku ei kaada mitään, se vain ohjaa seuraavan lukijan päättelemään väärin.
  • Omistajan tehtävä: healthchecks.io:n aikataulu on siirrettävä samaan aikaan (13 9 * * 0), muuten dead man's switch hälyttää myöhästymisestä jota ei ole.
v1.20.0

Lähetysajan todennettavuus ja ajastuksen ajautuman mitattu luku (H22, H23).

  • Lähetysloki kirjaa lähetysajan (H23). data/<pvm>/newsletter_sent.json sisältää nyt queued_at-, scheduled_at- ja message_ids-kentät sent-listan rinnalla. scheduled_at: null tarkoittaa varapolkua, eli lähetystä heti ilman ajastusta. Aiemmin lähetysaikaa ei tallennettu mihinkään, joten sitä ei voinut todentaa jälkikäteen. Muoto on taaksepäin yhteensopiva: load_sent, choose_recipients ja check_freshness.py lukevat yhä pelkkää sent-listaa, ja vanhat viikot kelpaavat sellaisenaan. Ensimmäisen erän aikaleimoja ei ylikirjoiteta, jotta myöhemmin herännyt varmistusajo ei peitä sitä että kirje lähti alun perin ajastettuna.
  • Tuoreusvalvonta erottaa ajastetun lähetyksen varapolusta (H23). check_freshness.py tulostaa koneluettavan LAHETYSAIKA:-rivin (ajastettu / varapolku / tuntematon) ja freshness.yml nostaa varapolusta ::warning::-huomion. Varapolku ei muuta tarkistuksen tulosta eikä nosta hälytystä "kirje ei lähtenyt": kirje meni perille, vain myöhässä, ja väärä otsikko ohjaisi etsimään vikaa väärästä paikasta.
  • Ajastimen ajautuman luku korjattu mitatuksi (H22). Väite "31–60 min" oli kuudessa paikassa (weekly-digest.yml, send_newsletter.py, AGENTS.md, README.md, CHANGELOG.md, site/mita-uutta.html). Mitattu ajautuma 19.7.–6.9.2026 on 23 min – 3 h 50 min, ja sisarrepossa sote-uutiset 5 h 10 min. Luku ei ole kosmeettinen: 6.9.2026 ajautuma oli +2 h 48 min eli yli cronin 2 h 47 min puskurin, jolloin scheduled_send_at palautti None ja kirje lähti heti klo 20:04 kuudelle oikealle tilaajalle. Tämä oli ensimmäinen kerta kun varapolku laukesi tuotannossa oikeille tilaajille. Cronia ei muutettu — puskurin kasvattaminen on omistajan päätös.
v1.19.0

HTML-suodatuksen ja varamuuntimen korjaukset, valvontaketjun tiukennus sekä API-kerroksen suojaus ja toistettavuus (erä 4 / tarkastuskierros 9).

  • HTML-suodatuksen taulukkokohdistus (D5): sanitize_html kääntää taulukoiden text-align-tyylit align-attribuuteiksi (left, right, center), jotta WeasyPrint kohdistaa numeeriset ja tekstisarakkeet oikein ilman että yleistä style-attribuuttia tarvitsee sallia.
  • Tagien lainausmerkkitietoisuus ja self-closing tagit (H80, H82): Tagien attribuutit suodatetaan lainausmerkit huomioivalla jäsentimellä, ja itsesulkeutuvat tagit (kuten <hr/> ja <br/>) normalisoidaan standardimuotoon (<hr />). Lainausmerkkitietoisen haun rinnalla on pakollinen varapolku (H82): pariton lainausmerkki ei täsmää lainausmerkkitietoiseen hakuun lainkaan, joten ilman varapolkua koko tagi olisi ohittanut sanitoinnin ja <script>, <img> tai <iframe> olisi päätynyt sivulle elävänä elementtinä. Vartija käyttää oraakkelina HTML-jäsennintä, ei merkkijonohakua.
  • _inline-varamuuntimen NUL-suojaus (H81): Varamuunnin poistaa mahdolliset NUL-tavut syötteestä ennen paikkamerkkien luontia, jotta koodijaksojen suojamerkit eivät voi törmätä syötteen sisältöön.
  • HTML-suodatuksen ja varapolun testivartijat (C13, C14, C15): Lisätty testivartijat sille, että render.py:n main()-polku ja muutoslokisivun renderöinti ajavat tulosteensa aina sanitize_html:n läpi, sekä sille, että Markdown-varapolku toimii itsenäisesti ilman ulkoista markdown-kirjastoa.
  • Dokumentaatiovartija (AT15): HANDOFF.md:stä siivottu vanhentunut kaksoisotsikko ja lisätty testivartija otsikkorakenteelle.
  • Valvonnan ja tuoreustarkistuksen kattavuus (H53–H58): check_freshness.py raportoi ja diagnosoi puuttuvat uutiskirjeet, virheelliset manifestit ja tulevaisuuden päivämäärät; komentoriviparametrit --date ja --site mahdollistavat täyden paikallisen testattavuuden ilman verkkokutsuja.
  • Peruutetun tilauksen palautus (H83): add_contact kääntää unsubscribed-lipun takaisin vain kun kutsuja pyytää sitä eksplisiittisesti (allow_resubscribe=True), ja vahvistuspolku confirm.py tekee juuri niin: tuore, allekirjoitettu ja enintään 24 h ikäinen tunniste on todiste uudesta suostumuksesta. Ilman tätä /tilaa-polku oli peruuttaneelle pysyvä umpikujaupdate_contact kirjoittaa vain first_name:n, joten mikään muu kohta ei palauta tilausta, ja vahvistussivu kertoi silti tilauksen onnistuneen. Samalla korjattu suojan sattumanvaraisuus: kontaktihaun kaatuessa tila jäi tuntemattomaksi ja peruuttanut heräsi henkiin.
  • API-kerroksen toistettavuussuoja ja virhekäsittely (AT9–AT12): Tilausvahvistustunnisteen voimassaoloaika lyhennetty 24 tuntiin ja vahvistussivun toistuva käyttö suojattu (olemassa olevia tilauksia ja niiden asetuksia ei ylikirjoiteta). Resendin 422-virheet käsitellään subscribe.py:ssä hallitusti ja /api/preferences-käsittelijä erottaa Resendin 404- ja 5xx-virheet sekä ohjaa aiemmin peruuttaneen tilaajan suoraan uudelleentilaussivulle.
  • API-kerroksen lisäsuojaukset ja yhtenäistykset (AH15–AH21): Eroteltu puuttuva kontakti (404) tyhjästä nimikentästä (200 Kaikki erikoisalat), uutiskirjeen lähetin strippaa salaisuuden välilyönnit, paikallisen PDF-manifestin luku suojattu poikkeuksilta, base64-dekoodaus hylkää kielletyt merkit (validate=True), kontaktilistan haulle lisätty muistinvarainen välimuisti, erikoisalojen nimet keskitetty _common.py:hyn ja subscribe.py:n 303-uudelleenohjaukseen lisätty tarvittavat välimuisti- ja pituusotsakkeet. Keskityksen vartija lukitsee myös konkreettiset arvot: aiempi prefs.X == common.X -vertailu oli tautologia, koska molemmat viittasivat samaan importattuun olioon.
v1.18.0

HTML-attribuuttien sallintalista, shell-syntaksin tarkistus ja putkivartijoiden täydennys (tarkastuskierros 8 / erä 3A).

  • HTML-attribuuttien sallintalista (H75): sanitize_html suodattaa nyt myös tagien attribuutit (_ALLOWED_ATTRS = {"href", "title", "id", "colspan", "rowspan", "align"}) ja pudottaa kaikki muut (kuten onclick, onmouseover, style). Lisäksi href-suodatus laajennettu kattamaan kaksoislainausmerkkien ohella yksinkertaiset lainausmerkit ja lainausmerkittömät arvot.
  • PDF-kaksoisescapoinnin vartija (C8): Varmennettu, ettei personoidun PDF-liitteen polku (render_filtered_pdf) tee esiescapointia ja että koodijaksot sekä tilastolliset operaattorit renderöityvät oikein.
  • Artikkelien duplikaattikarsinnan kutsutason vartija (C9): Varmennettu testillä, että fetch_articles.main() kutsuu karsintafunktiota ja poistaa aiemmissa katsauksissa esiintyneet PMID:t levylle kirjoitettavasta tuloksesta.
  • Esipuheen poiston pariteettivartija molempiin suuntiin (C10): test_render_main_strips_preamble parametrisoitu kattamaan sarkaimella erotetut otsikot (#\t), mikä varmistaa että main()-polku ei kavennu vanhaan regexiin.
  • Shell-skriptien syntaksitarkistus (H77, C12, H79): Jokaisen scripts/*.sh -skriptin syntaksi tarkistetaan bash -n:llä sekä CI:n omana askeleena että yksikkötestinä test_shell_scripts_are_syntactically_valid. CI-askel ajaa bash -n:n silmukassa tiedosto kerrallaan: bash -n a.sh b.sh tarkistaisi vain a.sh:n ja antaisi lopuille positionaalisen argumentin roolin, jolloin rikkinäinen scripts/setup_venv.sh ei kaataisi askelta. test_ci_shell_syntax_step_checks_every_script suorittaa CI-askeleen ja varmistaa, että se huomaa syntaksivirheen myös aakkosjärjestyksessä viimeisessä skriptissä.
  • _inline-varamuuntimen regressioiden korjaus (H78): Varamuunnin käyttää paikkamerkkimenetelmää parillisille backtick-jaksoille. Parittomat backtickit säilyvät literaaleina eikä lihavointi koodijakson ympärillä (**`koodi`**) rikkoudu.
v1.17.0

Uutiskirjeen lähetysketjun varmennus, idempotenssisuoja ja putken testivartijat (tarkastuskierrokset 6 ja 7).

  • Uutiskirjeen lähetys suojattu virheiltä (H40, C2): Jos yksittäisen tilaajan PDF-liitteen muodostus epäonnistuu, virhe ei enää keskeytä muiden viestien lähetystä. Jo lähetettyjen tilaajien kirjaus varmistetaan aina (finally), mikä estää kaksoislähetykset.
  • Lähetyslokin tallennuksen uudelleenyritys (H29, C4): Viikkoajon lähetyslokin git push yrittää neljästi takaisinväistöllä ja kaatuu virhekoodiin 1, jos tallennus ei onnistu.
  • Päiväkohtainen idempotenssisuoja ja pelastusajo (H27, H73, C4): Viikkoajo kieltäytyy generoimasta uudelleen jo julkaistua päivää (site/manifest.json). Jos uutiskirjettä ei ole lähetetty (newsletter_sent.json puuttuu tai on tyhjä), pelastusajo ohittaa vain generoinnin (SKIP_GENERATION=1) ja suorittaa uutiskirjeen lähetyksen. Epätyhjän lokin ylikirjoitus vaatii liput FORCE_REGENERATE=1 ja FORCE_REGENERATE_SENT=1.
  • HTML-suodatus ja koodijaksojen korjaus (D1, H46, H51): Siirretty HTML-suodatus Markdown-muunnoksen jälkeiseksi (sanitize_html), mikä korjaa koodijaksojen ja vertailuoperaattoreiden kaksoisescapoinnin (kuten <code> ja <pvm>) sallien turvalliset tagit ja attribuutit ja neutraloiden raa'at <script>- ja <img>-tagit myös suodatetuissa PDF-liitteissä.
  • Tiedostosuhteelliset polut API-kutsussa (AT13, C1): get_latest_digest_pdf käyttää tiedostosuhteellista polkua levylukuihin, mikä estää tarpeettomat verkkohaut ja tekee toiminnasta työhakemistosta riippumatonta.
  • Vanhat koosteet säilyttävät julkaisuversionsa (H74): Versionosto ei enää renderöi vanhoja koosteita uudelleen, jolloin niiden fontit ja tuotantoaikaleimat eivät muutu.
  • Testien laadunvalvonta CI:ssä (H48, C6, C7): tests/-hakemisto lisätty CI:n tyyppitarkistukseen (--check-untyped-defs) ja lintiin. CI asentaa tarvittavat fontit (fonts-liberation, fonts-dejavu-core) weasyprintille.
  • Putken luotettavuustestit ja invarianttivartijat (H37, H38, H39, H44, H47, H49, H50, H52, C5): Varmennettu artikkelien duplikaattikarsinta historiasta, esipuheen poisto eri poluissa (myös sarkaimella erotetut otsikot), tuntemattomien erikoisalojen käsittely ja oikean PDF-moottorin käyttö.
  • Tilausvahvistuksen teksti (AT8): Vahvistussivulta poistettu virheellinen lupaus erillisestä tervetulosähköpostista.
v1.16.0

Tilauksen vahvistus ja peruutus vaativat nyt painikkeen painalluksen.

  • Mikä muuttui: Sähköpostin linkin avaaminen näyttää vahvistussivun, jolla on painike. Vasta painikkeen painallus vahvistaa tilauksen tai peruuttaa sen. Aiemmin pelkkä linkin avaaminen riitti.
  • Miksi: Työsähköpostien turvaohjelmat (esimerkiksi Microsoft Defenderin Safe Links) avaavat viestien linkkejä automaattisesti tarkistaakseen ne. Ne saattoivat siis vahvistaa tilauksen kenenkään klikkaamatta — jolloin osoite päätyi listalle ilman että sen omistaja oli sitä varmistanut — tai peruuttaa tilauksen huomaamatta, jolloin lehti vain lakkasi tulemasta. Peruutuslinkki on voimassa viisi vuotta, joten riski säilyy pitkään.
  • Peruutus ei vaikeudu: Sähköpostiohjelman oma "peruuta tilaus" -toiminto (Gmail, Yahoo, Outlook) toimii edelleen yhdellä klikkauksella. Vahvistussivu koskee vain viestin tekstissä olevaa linkkiä.
  • Tilannekuvan tarkistus: scripts/check_handoff.py ei enää hälytä turhaan päähaarassa. Se erottaa nyt normaalin mergen jälkeisen tilan aidosta vanhentumisesta.
v1.15.3

Tarkastuskierroksen valvonta ja kaksi päätöstä ihmiselle (AH1, AH14, H15).

  • HANDOFF.md:n tilannekuvan tarkistus (H15): Uusi scripts/check_handoff.py vertaa tiedoston SIJAINTI-osiota gitin todelliseen tilaan (haara, versio, mainin commit, onko työ pushattu) ja kaatuu, jos jokin väite on väärä. Tiedosto pysyy käsin kirjoitettuna — tarkistus vain estää sitä valehtelemasta. Tämä on aito ongelma: 20.8.2026 vanhentunut tilannekuva ohjasi tarkastuksen väärään haaraan, jolloin edellisen kierroksen korjaukset päätyivät tuotantoon tarkastamattomina.
  • Kaksi kysymystä siirretty ihmisen päätettäväksi (AH1, AH14): Sekä tilauksen vahvistus että peruutus tapahtuvat linkin avaamisella, jolloin sähköpostin turvaskanneri voi laukaista ne käyttäjän puolesta. Vahvistuksessa se loisi tilaajan ja lähettäisi tervetuloviestin; peruutuksessa se poistaisi aidon tilaajan huomaamatta, ja peruutuslinkki on voimassa viisi vuotta. Korjaus (painike linkin sijaan) lisäisi klikkauksen, joten kyse on punninnasta, ei teknisestä valinnasta.
  • Konventiot kirjattu AGENTS.md:hen: versionoston vaatima uudelleenrenderöinti, selaintarkistusten työkalujen lainaaminen ja tarkastuskierroksen haaran todentaminen.
v1.15.2

Tarkastuskierroksen 5 havaintojen korjaukset (AH12, AH13, H14).

  • "Mitä uutta?" -sivun otsikot (H14): Muutoslokin tiivistelmärivillä käytetty inline-koodi näkyi sivun otsikoissa raakoina backtickeinä (`api/`), vaikka sama merkintä renderöi rungossa <code>-elementit oikein. Otsikot menevät nyt saman muotoilun läpi. Korvaus tehdään escapoinnin jälkeen, jottei CHANGELOG.md:stä pääse HTML:ää sivulle (K1/T4).
  • Vartijatesti, joka ei vartioinut (AH13): test_error_pages_do_not_duplicate_heading_as_lede kovakoodasi otsikko–runko-parit, joten se vertasi testin omaa vakiota runkovakioon. Koko testisarja meni läpi, vaikka yhden virhesivun otsikko palautettiin korjausta edeltäneeseen arvoon. Testi ajaa nyt kahdeksan virhepolkua oikeiden käsittelijöiden läpi ja lukee otsikon samasta vastauksesta kuin rungon; ledetön virhesivu ei enää ohitu hiljaa.
  • Dokumentaatio (AH12): ARCHITECTURE.md:131 väitti docs-check-työnkulun vartioivan README/HANDOFF-lohkoja; kohteet ovat README/ARCHITECTURE. Kierroksen 4 korjaus osui riviin 102 mutta ohitti tämän.
  • HANDOFF.md (H15): Kirjoitettu uusiksi; se kuvasi haaratilannetta, joka oli ohitettu kahdesti.
v1.15.1

Korjattu uutiskirjeen lähetys, joka keskeytyi liitteen muodostukseen.

  • Vika: Viikon 33 kooste (16.8.2026) julkaistiin verkkosivulle normaalisti, mutta uutiskirje ei lähtenyt tilaajille: lukijakohtaisen PDF-liitteen kokoaminen keskeytyi virheeseen. Vika tuli mukana lukijakohtaisten uutiskirjeiden myötä (versio 1.9.0) ja ilmeni ensimmäisessä sitä seuranneessa viikkoajossa.
  • Korjaus: Artikkelilistan lukeminen yhtenäistetty samaksi kuin muualla putkessa. Viikon 33 kirje lähetettiin tilaajille jälkikäteen 17.8.2026.
  • Varmistus jatkoa varten: Lisätty testit, jotka havaitsevat vastaavan virheen ennen julkaisua.
  • Lähetysmerkinnän tallennus korjattu: Merkintä lähetetystä kirjeestä jäi tallentumatta viikkoajossa, minkä vuoksi valvonta ilmoitti kirjeen puuttuvan vaikka se oli lähtenyt — ja uusinta-ajo olisi voinut lähettää saman kirjeen kahdesti. Merkintä tallentuu nyt automaattisesti.
  • Versionumero päivittyy kaikille sivuille automaattisesti: Aiemmin Tietoja- ja Tilaa-sivujen versiotieto piti päivittää käsin, jolloin se saattoi jäädä jälkeen muusta sivustosta.
v1.15.0

Sivuston versionhallinta, alatunnisteen versioilmoitus ja "Mitä uutta?" -sivu.

  • Alatunnisteen versioilmoitus ja linkki: Kaikkien sivujen alatunnisteeseen (.footer-fine ja .footer-links) lisätty ilmoitus nykyisestä sovellusversiosta ja suora linkki "Mitä uutta?" -sivulle (mita-uutta.html).
  • Uusi "Mitä uutta?" -sivu (site/mita-uutta.html): Esteetön ja selkeästi jäsennelty versiohistoriasivu, joka esittelee kunkin version uudistukset ja parannukset.
  • Automaattinen generointi: "Mitä uutta?" -sivu generoidaan automaattisesti CHANGELOG.md:stä ja VERSION-tiedostosta osana sivuston renderöintiä (scripts/render.py), jolloin sivuston muutosloki pysyy aina automaattisesti ajan tasalla.
v1.14.0

Uudet seurattavat julkaisut (20 → 25 lehteä).

  • Lisätty seurantaan 5 uutta lehteä (config/journals.json, site/tietoja.html, README.md, ARCHITECTURE.md):
    • Diabetes Care (Diabetes Care, yleis-/aikuislehdet)
    • Diabetes, Obesity and Metabolism (Diabetes Obes Metab, yleis-/aikuislehdet)
    • Diabetes Technology & Therapeutics (Diabetes Technol Ther, yleis-/aikuislehdet)
    • Diabetologia (Diabetologia, yleis-/aikuislehdet)
    • Acta Paediatrica (Acta Paediatr, lastentautien erikoislehdet)
  • Yleis- ja aikuislehdistä poimitaan lapsia koskevat artikkelit (pediatric_filter); lastentautilehdistä kaikki.
v1.13.1

Tarkastuskierroksen 4 havaintojen korjaukset (AH10–AH13).

  • AH10: Erotettu palvelimen konfiguraatiovirhe (ConfigError) virheellisestä linkistä api/preferences.py:n do_GET-metodissa (palauttaa nyt 500 _SERVER_ERROR eikä 400 "Linkki ei kelpaa").
  • AH11: Korjattu api/_common.py:n read_token-funktion kommentti vastaamaan nykytilaa putkimerkin osalta.
  • AH12: Korjattu ARCHITECTURE.md:ssä ollut kuvaus update_docs.py:n kohteista (README/ARCHITECTURE).
  • AH13: Poistettu virhesivujen otsikon toistuminen sanatarkasti ledenä kaikista 8 lomake- ja virhevastauksesta.
v1.13.0

Tervetuloviesti ja PDF-liite uusille tilaajille.

  • Uutiskirjeen tilausvahvistuksen yhteydessä lähetetään automaattisesti tervetuloviesti, jossa on liitteenä uusin sivustolla julkaistu ei-painotettu viikkokooste (PDF). Tervetuloviestin saatetekstissä informoidaan tilaajaa selkeästi, että kyseessä on ei-painotettu kooste ja jatkossa tulevat koosteet ovat heidän omien valintojensa mukaisesti painotettuja. Vercel-funktiot lukevat manifest.json:n ja tuoreimman PDF:n, jotka toimitetaan serverless-funktioille vercel.json includeFiles-määrittelyn avulla.
v1.12.1

Tumman tilan kontrastikorjaus ja erikoisalojen ehdollinen näyttö.

  • Kontrasti kuntoon tummassa tilassa: Valitun scope-laatikon taustaväriksi vaihdettu var(--accent-bg), jolloin teksti erottuu selkeästi ja korkealla kontrastilla sekä vaaleassa että tummassa tilassa.
  • Erikoisalojen ehdollinen aktivointi: Erikoisalojen valintaruudukko pidetään piilossa, kun Kaikki erikoisalat on valittuna, ja avataan näkyviin vasta kun tilaaja valitsee Vain valitut erikoisalat (toteutettu puhtaalla CSS :has() -säännöllä).
v1.12.0

Uutiskirjeen sisällön laajuusvalinta (Kaikki erikoisalat vs. Vain valitut erikoisalat).

  • Selkeä tilauskokemus: Tilaussivulla (site/tilaa.html) ja asetussivulla (api/preferences.py) on nyt oletuksena valittuna Kaikki erikoisalat (täysi viikkokooste ilman rajausta). Tilaaja voi halutessaan valita Vain valitut erikoisalat ja poimia kiinnostuksen kohteensa.
  • Puhdas JS-vapaa toteutus: Toimii 100 % ilman JavaScriptiä standardeilla radio- ja checkbox-elementeillä sekä CSS :has() -säännöillä sivuston tiukkaa CSP-tietoturvapolitiikkaa noudattaen.
  • Taustalogiikka ja testit: api/subscribe.py ja api/preferences.py käsittelevät scope-parametrin (all vs. custom). Lisätty 5 uutta yksikkötestiä (yht. 134 testiä).
v1.11.0

Diabetes omaksi valittavaksi kategoriaksi uutiskirjeeseen.

  • Uusi kategoria: Lisätty diabetes (13. erikoisala) config/categories.jsoniin, api/_common.py:n VALID_CATEGORIES-joukkoon ja prompts/summary_instructions.md:n luokitteluun ja ### Diabetes -väliotsikoksi.
  • Hierarkinen kytkös: scripts/filter_summary.py laajentaa endokrinologia-tilauksen kattamaan automaattisesti myös diabetes-artikkelit. Pelkän diabetes-kategorian valitseva saa koosteen vain diabeteksesta (ja nostot).
  • Käyttöliittymä: Lisätty Diabetes-valintaruutu tilaussivulle (site/tilaa.html) ja asetussivulle (api/preferences.py).
  • Testit: Päivitetty test_pipeline.py ja test_newsletter.py kattamaan 13 kategoriaa sekä diabeteksen ja endokrinologian suodatushierarkia.
v1.10.0

Tarkastuskierros 3:n api/-korjaukset.

  • Osoitevalidointi vastaa nyt Resendiä (AT6a). _EMAIL_RE hyväksyi mm. a|b@…, a/b@… ja a,b@…, joihin Resend vastaa 422: osoite kelpasi lomakkeella ja tilaus hajosi vasta vahvistuslinkin takana. Local part on nyt RFC 5322:n atext ilman |- ja /-merkkejä, verkkotunnus LDH-nimiöitä.
  • Resendin 422 on pysyvä virhe (AT6b). /api/unsubscribe ja /api/confirm palauttavat siitä 400 eivätkä 502:ta; teksti ei enää kehota yrittämään myöhemmin uudelleen, koska se ei koskaan auta.
  • Tyhjä kategoriavalinta hylätään (AT7). /api/preferences tallensi tyhjän valinnan, ja lähetysvaiheessa tyhjä kenttä laajenee tarkoituksella kaikiksi 12 kategoriaksi — kaikki rastit poistanut käyttäjä sai siis enemmän sisältöä. Nyt 400 eikä tallennusta lainkaan. Haun epäonnistuessa ei enää näytetä tyhjää lomaketta, joka pyyhkisi valinnat huomaamatta.
  • Vahvistusviesteille osoitekohtainen raja (AT4). Sama osoite saa enintään yhden viestin per 15 min; avaimena tiiviste, joten osoite ei jää muistiin selväkielisenä. Vanhentuneet avaimet siivotaan (AH4).
  • Suomenkielinen virhesivu myös toteuttamattomille metodeille (AH2). Uusi yhteinen kantaluokka _common.FinnishHandler; PUT /api/subscribe palautti ennen englanninkielisen oletussivun ja Cache-Control: public.
  • Kovennukset: Content-Length-suojaus preferences.py:hen (AH7), tunniste escapataan lomakkeen attribuuttiin (AH8), page() escapaa otsikon (AH5), </body> suljetaan (AH6).
  • AT3 mitattiin ja kumoutui. Nopeusrajoitinta ei voi ohittaa omalla X-Forwarded-For-otsakkeella: Vercel normalisoi otsakkeen. Koodia ei muutettu; mittaus on kirjattu kommenttiin, koska "oikeanpuoleisin arvo" olisi tässä ollut väärä korjaus.
  • Testit 106 → 126 (AH3): nopeusrajoitin, hunajapurkki, page() ja html_to_text olivat täysin testaamattomia.
  • render.py:n docstring ei enää lupaa julkaista articles.jsonia sivustolle — kopiointi poistui jo T5:ssä (AH9).
v1.9.1

Neutraali prompti ja personoitu PDF-johdanto.

  • Prompti neutralisoitu. Poistettu endokrinologian painotus summary_instructions.md:stä: nostot valitaan nyt tasapuolisesti kaikista erikoisaloista kliinisen merkittävyyden perusteella.
  • Kaikki 5 koostetta regeneroitu tekoälymallilla neutralisoidulla promptilla (DOI-kattavuus säilytetty).
  • Etusivun hero-teksti päivitetty: poistettu painotusvirke, lisätty personoinnin CTA uutiskirje-osioon (render.py INDEX_TEMPLATE).
  • Personoitu johdanto. filter_summary.py: uusi category_labels- parametri injektoi tilaajan valitsemat erikoisalat PDF-johdantoon ("Tämä kooste on räätälöity sinulle. Valitsemasi erikoisalat: …").
  • send_newsletter.py: välittää _CAT_LABELS-dict filter_summarylle.
  • site/tietoja.html, site/tilaa.html: päivitetyt tekstit kuvaamaan neutraali sivuston PDF + personoitu uutiskirje-PDF -mallia.
v1.9.0

Personoidut uutiskirjeet: tilaaja valitsee erikoisalat.

  • 12 kategoriaa. Artikkelit luokitellaan automaattisesti MeSH-termien ja avainsanojen perusteella (config/categories.json, fetch_articles.py).
  • Prompt-päivitys. Tekoälytiivistys ryhmittelee artikkelit 12 kategoriaan ja merkitsee ne [kategoria: X] -leimoin (prompts/summary_instructions.md).
  • Render-puhdistus. strip_category_tags() ja extract_category_tags() poistavat/lukevat leimat HTML:ssä ja PDF:ssä (render.py).
  • Tilauslomake. Checkbox-ruudukko erikoisaloille (site/tilaa.html, site/styles.css).
  • Token-laajennus. HMAC-token kuljettaa kategoriat puolipisteellä erotettuina (_common.py), read_token palauttaa (email, categories).
  • Resend-kontaktit. Kategoriat tallennetaan first_name-kenttään CSV-muodossa (confirm.py, _common.py).
  • Asetussivu. api/preferences.py — palvelinrenderöity lomake, jolla tilaaja voi muuttaa kategorioitaan allekirjoitetun linkin avulla ilman kirjautumista.
  • Uutiskirje. "Muokkaa kiinnostuksiasi" -linkki jokaisessa viestissä (send_newsletter.py), vastaanottajien kategoriat parsitaan kontaktista.
  • Suodatus. scripts/filter_summary.py suodattaa Markdown-tiivistyksen tilaajan kategorioiden mukaan (Nostot aina mukana).
  • 102 → 106 testiä; ruff, mypy puhtaat.
v1.8.1

Erä 1a: pysyvä virhetila ei enää esiinny tilapäisenä häiriönä

(ANALYSIS.md kierros 3, AT1–AT2 ja HANDOFF.md §6.3). Kolme kohtaa, yksi vika: - AT1 — putkimerkki osoitteessa rikkoi vahvistuksen pysyvästi. read_token purki hyötykuorman split("|", 2):lla, mutta | on RFC 5322:n mukaan laillinen merkki local partissa ja _EMAIL_RE päästää sen läpi. Osoitteella a|b@esimerkki.fi osoitteeksi tuli a ja vanhenemisajaksi b@esimerkki.fi|…, int() kaatui ja linkki oli pysyvästi rikki — ilman lokimerkintää syystä. Nyt käyttötarkoitus irrotetaan vasemmalta ja vanhenemisaika oikealta. - AT2 — kauttaviiva osoitteessa ohjasi peruutuksen väärään päätepisteeseen. quote() jättää oletuksena / koodaamatta, joten a/b@esimerkki.fi tuotti polun /contacts/a/b%40esimerkki.fi. Nyt safe='' (myös list_contacts). - §6.3 — peruutus tulkitsi Resendin 404:n häiriöksi. Osoite, jota ei ole yhteystietona, ei ole listalla — juuri sitä käyttäjä pyysi. Aiemmin näytettiin 502 "avaa linkki myöhemmin uudelleen", vaikka tilanne on pysyvä. RFC 8058:n yhden klikkauksen peruutuksessa Gmail ja Yahoo tulkitsivat 502:n epäonnistuneeksi peruutukseksi. Nyt 404 → 200. Muut virhekoodit yhä 502. - Viisi uutta testiä vartioivat kaikkia kolmea sekä sitä, ettei purkulogiikan löysentäminen päästä läpi hyötykuormaa ilman vanhenemisaikaa (76 → 81). - AT5 ei ole tässä erässä. Sen todentaminen vaatii tiedon siitä, mitä Resend vastaa duplikaattiyhteystiedolle, eikä sitä saa selville luomatta oikeaa yhteystietoa. Este oli 9.8. lähetys, joka olisi mennyt myös koekontaktille; se on nyt takana ja seuraava lähetys on 16.8., joten todennus on tehtävissä väliajalla (luo, mittaa, poista heti). Korjaus omana eränään sen jälkeen.

v1.8.0

Lähetysaika irti cronista + tuoreusvalvonta:

  • Uutiskirje on tilaajilla klo 20 Suomen aikaa riippumatta siitä milloin ajo laukeaa. Lähetys jonotetaan Resendiin scheduled_at-kentällä (scripts/send_newsletter.py:scheduled_send_at, api/_common.py:send_email). Syy: GitHubin ajastin ei ole täsmällinen — tämän repon ajot ovat käynnistyneet 23 min – 3 h 50 min myöhässä (kirje 20:57–21:00), ja sote-uutiset-repossa 5 h 10 min. (Luku korjattu mitatuksi v1.20.0:ssa; v1.8.0:ssa arvio oli 31–60 min.)
  • Cron 0 17 * * 013 14 * * 0 (su 14:13 UTC). Ajo aikaistettiin, jotta lähetykselle jää n. 2 h 47 min puskuria. Jos ajo silti myöhästyy yli klo 20:n, scheduled_send_at palauttaa None ja kirje lähtee heti — menneeseen hetkeen ei voi ajastaa, eikä myöhässä oleva sunnuntaikirje kuulu seuraavaan päivään.
  • Kesä- ja talviaika korjaantuivat sivutuotteena. Vanha cron osui klo 20:aan vain kesällä ja klo 19:aan talvella. Aika lasketaan nyt Europe/Helsinki-vyöhykkeellä.
  • Uusi tuoreusvalvonta (.github/workflows/freshness.ymlscripts/check_freshness.py, ma 06:47 UTC): tarkistaa että odotetun sunnuntain kooste on manifestissa ja että data/<pvm>/newsletter_sent.json on olemassa eikä tyhjä. Sulkee heartbeatin sokean pisteen: run_weekly.sh ei kaada ajoa lähetyksen epäonnistuessa, joten valvonta on voinut näyttää vihreää vaikka kirje ei lähtenyt (näin kävi 9.8.2026). Vain luku, pelkkä vakiokirjasto.
  • HUOM ylläpidolle: healthchecks.io-checkin aikataulu on päivitettävä vastaamaan uutta cronia 13 14 * * 0, muuten se näyttää myöhässä olevalta joka sunnuntai.
v1.7.4

CLI-varapolun mallitunnus ajan tasalle:

  • tiivistimen oletusmalli päivitetty uudempaan tunnukseen (scripts/run_weekly.sh:37 ja README:n ympäristömuuttujalohko). Vanha tunnus ei vastaa mitään olemassa olevaa mallia: genai-rajapinta palauttaa sille 404 NOT_FOUND, kun taas models.list tarjoaa kaksi uudempaa tunnusta.
  • Ajastettuun ajoon tällä ei ole vaikutusta. Oletus on rajapintatila, joka lukee mallin asetustiedostosta; muuttuja koskee vain paikallista komentorivivarapolkua. Nyt molemmat polut osoittavat samaan malliin — juuri siksi virhe ehti jäädä huomaamatta, sillä rikkinäistä polkua ei ajeta CI:ssä.
  • Löytyi API-avaimen vaihdon yhteydessä ajetussa savutestissä, jossa kaikki repon käyttämät mallitunnukset kutsuttiin läpi uudella avaimella.
v1.7.3

Kuollut RESEND_AUDIENCE_ID pois + väitteet todennettu paikallisesti:

  • RESEND_AUDIENCE_ID poistettu .github/workflows/weekly-digest.yml:stä. Resendin Audiences-ratkaisusta luovuttiin globaalien yhteystietojen hyväksi (api/_common.py:229), mutta työnkulun env-rivi jäi jäljelle. gh secret list osoittaa, ettei salaisuutta ole olemassa: rivi laajeni tyhjäksi merkkijonoksi eikä rikkonut mitään, minkä vuoksi se jäi huomaamatta. tests/test_newsletter.py:199 vartioi koodipolkua, ei työnkulkutiedostoa.
  • Sama kuvio kuin 1.7.2:ssa: valmiiksi merkityn työn keskeneräinen siivous, ei uusi vika.
  • Todennukset, joita pilvihiekkalaatikko ei voinut ajaa, ajettiin macOS:llä: testisarja 71/71 (löydösten H3/H4 varsinainen alusta), dig DMARC/SPF/DKIM:lle, tuotannon curl (julkaistu versio 1.7.2) ja gh CI:lle. Punainen main osoittautui GitHubin Actions-häiriöksi 6.8. — ei koodivika; uudelleenajot 7.8. menivät läpi 29 s ja 12 s. Yksityiskohdat: HANDOFF.md §2 ja §5.
v1.7.2

docs-check-työnkululle aikakatto:

  • timeout-minutes: 10 lisätty .github/workflows/docs-check.yml:ään. Se oli ainoa työnkulku ilman aikakattoa: tests.yml (10) ja weekly-digest.yml (30) saivat sen ANALYSIS-löydöksen T6 korjauksessa, mutta tämä jäi väliin. Ilman kattoa jumiutunut ajo varaa runnerin GitHubin kuuden tunnin oletusrajaan asti.
  • Puute löytyi todentamalla, pitääkö T6 yhä — ei uudesta muutoksesta.
v1.7.1

Dokumentaatio jaettu kahtia elinkaaren mukaan:

  • ARCHITECTURE.md (uusi) — pysyvä tekninen viite: arkkitehtuuri, putki, asetukset, toimitukselliset säännöt ja ratkaistut sudenkuopat. Muuttuu harvoin.
  • HANDOFF.md — enää pelkkä istunnon tilannekatsaus (129 riviä): missä työ on, mikä on todennettu ajamalla ja mitä ympäristössä ei voitu testata. Kirjoitetaan uusiksi joka istunnossa. Nimi säilyi tällä tiedostolla, koska toistuva handoff-kehote kirjoittaa nimenomaan HANDOFF.md:hen — muuten jokainen istunto ylikirjoittaisi viitteen.
  • Uutiskirjeosio siirretty järjestykseen. Se oli numeroitu §14:ksi mutta sijaitsi §11:n ja §12:n välissä. Nyt lopussa, joten §1–§13 säilyttävät numeronsa eivätkä AGENTS.md:n ristiviittaukset (§7, §9, §13) katkea.
  • AUTO-STATUS-lohko seuraa mukana: scripts/update_docs.py, docs-check.yml ja run_weekly.sh osoittavat nyt ARCHITECTURE.md:hen. HANDOFF.md:ssä ei ole enää generoitua sisältöä, joten sen voi kirjoittaa vapaasti uusiksi ilman että CI kaatuu.
v1.7.0

Macin launchd-varajärjestelmä poistettu; ajastus on vain GitHub Actionsissa:

  • launchd/fi.bwwf.pediatrics-digest.plist ja scripts/install_launchd.sh poistettu. Varajärjestelmä ei ollut turvaverkko vaan riski: se olisi julkaissut saman päivän katsauksen toiseen kertaan Actionsin rinnalla, ja sen launchd-ympäristöstä puuttui API-avain, joten jokainen ajastettu ajo kaatui riviin API-avain puuttuu — viimeksi 2.8.2026. Toinen julkaisukykyinen ajoympäristö, joka ei ole koskaan onnistunut, on ylläpitovelkaa.
  • Käsiajo säilyy: ./scripts/run_weekly.sh (esikatseluun GIT_PUSH=0) toimii yhä paikallisesti. Vain ajastus poistui.
  • Dokumentaatio siivottu README/HANDOFF/AGENTS: kaksoisajovaroitukset, plistin EnvironmentVariables-ohjeet ja asennusaskeleet pois; HANDOFF §5 kertoo nyt myös miksi varajärjestelmä poistettiin, jottei sitä lisätä takaisin.
  • run_weekly.sh:n virheilmoitus ei enää ohjaa tiedostoon logs/run.log — sen kirjoitti launchd-plistin uudelleenohjaus, ei skripti itse.
v1.6.6

Väliaikainen diagnostiikka pois, kun uutiskirjeketju on todettu toimivaksi:

  • api/diag.py poistettu. Se oli alusta asti merkitty väliaikaiseksi. Tilaus → vahvistus → peruutus on nyt todettu toimivaksi päästä päähän tuotannossa, joten päätepiste ei ole enää perusteltu — se ei paljastanut salaisuuksien arvoja, mutta julkinen ajoympäristön kuvaus on turhaa pintaa. Tiedosto löytyy git-historiasta, jos vastaava läpinäkymätön vika joskus palaa.
  • Vanhentuneet Audience-maininnat korjattu koodin dokumenttimerkkijonoissa ja viikkoajon työnkulussa. RESEND_AUDIENCE_ID ei ole ollut olemassa sitten v1.6.2:n, mutta työnkulun kommentti listasi sen yhä pakollisena salaisuutena. Samalla kirjattu, että avaimen on oltava Full access — pelkkä "Sending access" ei riitä.
  • HANDOFF: neljän tuotantovian ketju ja diagnostiikkapäätepisteen rooli kirjattu muistiin, jotta seuraava lukija tietää mistä aloittaa vastaavassa tilanteessa.
v1.6.5

Roskapostivaroitus kevennetään, kun viestit alkoivat mennä perille:

  • Tilaussivun näkyvä varoitus poistettu. Ennen tilaamista se lähinnä herätti epäilyn palvelun toimivuudesta. Poiston peruste on kirjattu HTML-kommenttina tiedostoon, jottei sitä lisätä takaisin vahingossa.
  • Onnistumissivun ohje lyhennetty yhdeksi riviksi, joka mainitsee yhä lähettäjän uutiskirje@pediatrics.fi. Sitä ei poistettu: pediatrics.fi on yhä nuori lähettäjä, eikä yhden postilaatikon onnistuminen yleisty muihin.
  • Testi kääntyi ympäri: aiempi test_subscribe_page_warns_about_the_spam_folder korvattiin testillä, joka vaatii ohjeen onnistumissivulta ja kieltää sen tilaussivun näkyvästä sisällöstä. Molemmat puolet todennettu kaatumaan.
  • DMARC (p=none, raportit dmarc@pediatrics.fi) on lisätty nimipalveluun; dokumentoitu HANDOFF:iin. Tekstiversio ja DKIM-kohdistus säilyvät ennallaan.
v1.6.4

Vahvistusviesti löydettävämmäksi ja vähemmän roskapostimaiseksi:

  • Tekstiversio jokaiseen viestiin. Pelkkä HTML on tunnettu roskapostisuodattimien miinus, ja iCloud on tässä erityisen tiukka. html_to_text() avaa linkit muotoon "teksti (URL)", jotta osoite näkyy myös tekstiversiossa. Testi vartioi, ettei tekstiversio pääse katoamaan.
  • Valinnainen NEWSLETTER_REPLY_TO — oikea vastausosoite parantaa mainetta.
  • Roskapostihuomautus näkyviin sekä tilaussivulle että lähetyksen onnistumissivulle: lähettäjäosoite ja otsikko kerrotaan, jotta viestin osaa etsiä, ja kehotetaan merkitsemään se "Ei roskapostia".
  • Huom: nämä eivät korjaa uuden lähetysdomainin puuttuvaa mainetta, joka on todennäköisesti pääsyy. Se paranee ajan ja positiivisen vuorovaikutuksen myötä.
v1.6.3

Korjattu Cloudflaren torjuma Resend-kutsu:

  • Tilaus kaatui tuotannossa virhesivuun. Diagnostiikkakoetin paljasti syyn: 403 ja rungossa error code: 1010 — se on Cloudflaren, ei Resendin, virhe. Pyyntö ei siis päätynyt Resendille lainkaan.
  • Syy: urllib.request lähettää oletuksena tunnisteen Python-urllib/3.x, jonka Cloudflaren bottisuojaus torjuu. Tämä olisi kaatanut kaiken — tilauksen, vahvistuksen, peruutuksen ja viikkolähetyksen.
  • Korjaus: oma User-Agent (ja Accept: application/json). Regressiotesti varmistaa, ettei urllibin oletustunniste palaa; testi kaatuu ilman otsaketta.
  • Huomionarvoista: lähetysdomain oli koko ajan verifioitu — ilman koetinta vika olisi jäänyt helposti domainin syyksi.
v1.6.2

Päivitetty Resendin nykyiseen rajapintaan:

  • Audienceja ei enää ole. Resend nimesi ne Segmenteiksi ja teki yhteystiedoista globaaleja: yhteystieto tunnistetaan sähköpostiosoitteesta eikä kuulu enää yhteen Audienceen. Toteutus oli kirjoitettu vanhaa rajapintaa vasten (/audiences/<id>/contacts), joka ei enää ole olemassa.
  • Päätepisteet nyt: POST /contacts, PATCH /contacts/{email}, GET /contacts.
  • RESEND_AUDIENCE_ID poistettu kokonaan — sitä ei ole enää mistä ottaa eikä sitä tarvita. Tilaajalista on yksinkertaisesti ne yhteystiedot, joiden unsubscribed on false. Käyttöönotossa ei siis luoda mitään listaa.
  • Sivutus toteutettu. Resendin listaus on kursoripohjainen ja palauttaa vain osan kerralla. Ilman has_more-seurantaa yli yhden sivun tilaajalista katkeaisi hiljaa: osa tilaajista jäisi ilman kirjettä eikä mikään näyttäisi rikkinäiseltä. Testi vartioi sivutusta ja silmukan katkaisua.
v1.6.1

Uutiskirjeen korjaukset:

  • Viesti on nyt PDF-keskeinen. Aiempi versio listasi nostojen otsikot viestin rungossa, mikä oli käytännössä kooste tekstimuodossa. Nyt runko on lyhyt saate (viikko + yksi tiivistysrivi + linkki verkkoversioon) ja varsinainen sisältö on PDF-liitteessä. Testi vartioi, ettei runko ala kasvaa: ei luetteloita, alle 900 merkkiä. highlight_titles poistettu tarpeettomana.
  • PDF on nyt pakollinen. Koska liite on viestin sisältö, lähetys keskeytyy virheeseen jos PDF puuttuu — aiemmin viesti olisi lähtenyt ilman sitä.
  • Korjattu funktioiden tuonti Vercelillä. Tuotannossa kaikki /api/-päätepisteet palauttivat 500 — myös /api/subscribe:n GET, joka vain ohjaa /tilaa-sivulle eikä käytä yhtään ympäristömuuttujaa. Se sulki pois puuttuvat salaisuudet ja osoitti, ettei sisarmoduulin _common tuonti onnistu. Korjattu lisäämällä funktion oma hakemisto sys.pathiin, pakottamalla includeFiles: "api/**" ja kiinnittämällä Python-versio (3.12).
  • Lisätty väliaikainen api/diag.py, joka kertoo ajoympäristön tosiasiat (Python-versio, hakupolku, tuonnin tulos, mitkä muuttujat on asetettu — ei arvoja). Poistetaan kun toiminta on todettu.
  • Opetus: Vercelin "Ready" kertoo vain että käännös onnistui, ei että funktiot ajautuvat. Päätepisteet on kutsuttava erikseen.
v1.6.0

Uutiskirje: tilaus, vahvistus, peruutus ja viikkolähetys.

Mallina omistajan Sotekooste-projekti (README + siirrettävä toteutuskuvaus). - Tilaus ilman JavaScriptiä. Tavallinen HTML-lomake (site/tilaa.html) POSTaa /api/subscribe-funktiolle. Ainoa CSP-muutos on form-action 'none''self'; script-src pysyy poissa, joten sivustolla ei edelleenkään ole JS:ää. Mitattu selaimella: 'none' estää lähetyksen, 'self' sallii sen. - Kaksivaiheinen tilaus ilman tietokantaa. Vahvistuslinkin tunniste on HMAC-allekirjoitettu ja sisältää vanhenemisajan (7 vrk). Vahvistamatonta osoitetta ei tallenneta minnekään; vasta vahvistus lisää sen Resend Audienceen. - Vahvistus- ja peruutustunnisteet on eriytetty (käyttötarkoitus on osa allekirjoitusta), joten vahvistuslinkillä ei voi peruuttaa tilausta eikä päinvastoin. - Kolme toisistaan riippumatonta lähetyssuojaa: SEND_EMAILS on oltava täsmälleen "true"; koeajo ei kirjaa lähetyslokiin; TEST_EMAIL ohittaa tilaajalistan. Lisäksi sisällötöntä koostetta (0 artikkelia) ei lähetetä. Käsin käynnistetty workflow-ajo on oletuksena koeajo. - One-Click Unsubscribe (List-Unsubscribe + List-Unsubscribe-Post, RFC 8058) jokaisessa viestissä — Gmail ja Yahoo vaativat sen massalähettäjiltä. - Idempotenssi ilman tietokantaa: data/<pvm>/newsletter_sent.json estää saman koosteen lähettämisen samalle osoitteelle kahdesti. - Sähköpostiosoitteita ei lokiteta selväkielisenä (m***@esimerkki.fi). - Funktiot eivät tarvitse riippuvuuksia: Resendin REST-rajapintaa kutsutaan urllib.requestilla. Tyhjä api/requirements.txt estää juuren weasyprintin asentumisen funktioihin. - 18 uutta testiä. Jokainen regressiotesti varmennettu palauttamalla vika hetkeksi — kaikki viisi kaatuivat vialla ja menevät läpi korjattuina. - CI laajennettu kattamaan api/ (ruff + mypy). - Korjattu matkan varrella: mobiilissa tilauskentästä tuli 16rem korkea, koska flex-basis tarkoittaa pystysuuntaisessa flex-kontissa korkeutta.

v1.5.2

Painotusalat näkyviin ingresseihin:

  • Etusivun hero-ingressiin ja tietoja-sivun ingressiin lisätty virke painotusaloista (lasten endokrinologia, aineenvaihduntataudit, lihavuus, harvinaissairaudet). Sivut kertoivat aiemmin vain kattavuuden, eivät painotusta. Sama sanamuoto molemmissa; etusivulla toisena virkkeenä, koska ensimmäinen päättyy jo kaksoispisteeseen.
  • Painotusalat vastaavat HANDOFF:iin kirjattua toimituksellista painotusta.
v1.5.1

Oma verkkotunnus pediatrics.fi käyttöön:

  • SITE_URL-oletus julkaisut.vercel.apppediatrics.fi. Tämä ohjaa RSS-syötteen absoluuttiset linkit ja og:url-tagit. Ympäristömuuttuja yliajaa edelleen (esikatseluympäristöt).
  • Lisätty rel="canonical" kaikille sivuille. Kaksi hostnamea (oma domain ja Vercelin *.vercel.app) tarjoilee identtistä sisältöä, mikä näyttäisi hakukoneille duplikaatilta ilman kanonista osoitetta. tietoja.html on käsin ylläpidetty, joten sen osoite on kovakoodattu — uusi testi varmistaa, ettei se karkaa SITE_URL:stä.
  • Kaikki koosteet renderöity uudelleen uusilla osoitteilla.
  • HANDOFF: uutiskirjeen este poistui — Resend vaatii verifioitavan lähetysdomainin, mikä ei onnistunut *.vercel.app-osoitteella mutta onnistuu nyt.
v1.5.0

Visuaalinen kieli linjattu sote-uutiset/Sotekooste-sivuston mallin

mukaiseksi (omistajan toimittamat kuvakaappaukset referenssinä): - Serif-otsikot takaisin, hallitusti. Otsikot, ingressit ja koostetiivistelmät käyttävät järjestelmän serifiä (Charter → Iowan Old Style → Georgia); käyttöliittymä, metatiedot ja versaalitunnisteet pysyvät sansissa. CSP kieltää fonttilataukset, joten kaikki fontit tulevat järjestelmästä. - Osiotunnisteet ovat nyt lyhyt värillinen viiva + harvennettu versaaliteksti ("— UUSIN KOOSTE"), ja niiden perässä voi olla pikkumerkki (artikkelimäärä). Uusi toissijainen aksenttiväri --accent-2 (kontrasti 6,8:1 / 7,1:1). - Litteä nosto. Nostokortin laatikko poistettu; erottelu tapahtuu viivoilla. Järjestys mallin mukaan: tunniste → otsikko → julkaisupäivä → viiva → tiivistelmä → toiminnot. Painikkeet ovat suuremmat ja mobiilissa täysleveät ja pinotut. - Lyhyt navigaatiotunnus. Koko nimi ei mahtunut navigaatiopalkkiin, joten palkissa on nyt "Lastentaudit" + vaaleampi selite, joka piilotetaan kaikkein kapeimmilla näytöillä. Aktiivinen sivu merkitään alleviivauspalkilla. - Arkisto: vuosiotsikkoon koostemäärä, riviotsikot serifillä. - Etusivun metarivi tiivistetty neljästä kohdasta kahteen ("Viikkokooste · Ilmestyy sunnuntaisin") — neljä versaalikohtaa rivittyi kahdelle riville. - Korjattu vanha bugi: sisällysluettelon linkit alleviivautuivat, koska .toc a ja .prose a ovat samalla spesifisyydellä ja jälkimmäinen tulee tiedostossa myöhemmin. Aiempi korjaus ei siis koskaan purrut. Valitsin on nyt .prose .toc a, ja regressiotesti lisätty.

v1.4.0

Visuaalinen ilme yksinkertaisemmaksi ja sivuston nimi vaihdettu:

  • Nimi: sivusto on nyt "Lastentautien artikkelien viikkokooste". Nimi tulee yhdestä paikasta (BRAND render.py:ssä) ja välittyy otsikoihin, navigaatioon, alatunnisteeseen, PDF-nimiöön ja RSS-syötteeseen; yksikkötesti estää nimen kovakoodaamisen templateihin.
  • Korjattu näkyvä bugi: etusivun otsikossa oli <br>, jonka mobiilisääntö piilotti — sanat valuivat yhteen ("Lastentautienviikkokatsaus") ja katkesivat kesken sanan. <br> poistettu ja regressiotesti lisätty.
  • Pienempi ja yksinkertaisempi typografia: perusfontti 17 px → 15 px, etusivun pääotsikko 54 px → 30 px, koostesivun otsikko 44 px → 27 px. Luovuttiin serif+sans-yhdistelmästä: koko sivusto käyttää nyt yhtä järjestelmäfonttia ja hierarkia syntyy koosta, painosta ja väristä.
  • Rauhallisempi ilme: poistettu koristepilleri ja tilastolohko herosta (yksi hillitty metarivi tilalle), kevyemmät reunat ja pinnat, ei varjoja, pienempi pyöristys. Etusivu mahtuu nyt yhdelle mobiiliruudulle.
  • Koostesivun otsikkona viikko (esim. "Viikko 30 / 2026") mallin joka viikko samanlaisen geneerisen otsikon sijaan; päällekkäinen viikkotunniste poistettu.
  • Sanasto yhtenäistetty: "katsaus" → "kooste" käyttöliittymässä.
v1.3.0

Sivuston rakenne, ulkoasu ja tyyli uudistettu ammattimaisemmaksi:

  • Rakenne. Katsaussivulle sisällysluettelo ("Sisällys") ja ankkuri-id:t kaikkiin osioihin. Korjattu vakava luettavuusongelma: "Muut julkaisut" -osiossa jopa 16 artikkelia niputtui yhdeksi kappaleeksi (markdown yhdistää peräkkäiset rivit) — nyt jokainen artikkeli on oma merkintänsä ja sen otsikko omalla rivillään (split_entry_lines, classify_paragraphs).
  • Etusivu. Uusin katsaus nostettuna omaan korttiinsa; arkisto kompakteina vuosittain ryhmiteltyinä riveinä (skaalautuu, kun katsauksia kertyy).
  • Tyyli. site/styles.css kirjoitettu uudelleen design-järjestelmänä: tokenit, sujuva (clamp) tyyppiasteikko, serif-otsikot + sans-leipäteksti, hillitty paletti vaalealle ja tummalle tilalle. Kaikki tekstikontrastit läpäisevät WCAG AA:n ja interaktiivisten elementtien rajat 1.4.11:n (3:1).
  • Saavutettavuus. "Hyppää sisältöön" -linkki, aria-current navigaatiossa, aria-labelit, näkyvät fokusrenkaat, scroll-padding ankkureille sekä prefers-reduced-motion-tuki. Ei vaakasuuntaista ylivuotoa 320–1280 px.
  • Tietoja-sivu jäsennelty: numeroidut vaiheet ja lehdet ryhmäkortteina.
  • PDF ja tulostus. PDF-pohjaan nimiö, sivunumerot ja selkeämmät osio-otsikot; selaimen tulostukselle omat @media print -säännöt.
  • PDF luodaan nyt ennen HTML-sivua, joten sivulle ei jää kuollutta latauslinkkiä.
  • Sivustotunnus (site/favicon.svg) — selaimen välilehdessä ei enää oletuskuvaketta eikä palvelimelle turhaa 404-pyyntöä.
  • Yhdeksän uutta yksikkötestiä rakennefunktioille (43 testiä yhteensä).
v1.2.3

Riippumattoman tarkastuksen (REVIEW.md, kierros 2) löydökset korjattu:

  • CI-korjaus: lint-/testityökalut kiinnitetty versioon tests.yml:ssä (kiinnittämätön ruff ajautui 0.16.0:aan ja kaatoi CI:n ilman koodimuutosta) ja ruff 0.16.0:n 9 löydöstä korjattu (mm. tz-tietoiset päiväykset, subprocess.run(check=False), suoritusoikeudet, dedup-lukuvirheen lokitus).
  • Poistettu data/2026-07-23/ — julkaisemattoman hakuajon artefaktit (tyhjä summary.md) saastuttivat dedup-historian: 25 julkaisematonta artikkelia olisi pudonnut hiljaa seuraavasta katsauksesta.
  • CHANGELOGin 1.2.1-merkinnän asiavirheet korjattu (H12: Vercel, ei GitHub Pages; H1/H2-kuvaukset) ja AGENTS.md:n testausohjeen muotoilu siistitty.
  • Vanhentunut ANALYYSIRAPORTTI.md poistettu jo aiemmin (commit 03355be; sisältö korvautunut ANALYSIS.md + REVIEW.md -tiedostoilla) — kirjataan tässä, koska poisto tehtiin alun perin korjausscopen ulkopuolella.
v1.2.2

Viimeistely (Erä 3/3):

  • PubMed-sivutus (E-utilities) korjattu ehkäisemään ikuissilmukka, kun palautettu lista sisältää vain aiemmin käsiteltyjä osumia (T2).
  • Prompt-injektiosuoja lisätty AI-tiivistysohjeisiin, jotta malli ei tottele artikkelidatan sisäisiä käskyjä (H11).
  • Testikattavuutta parannettu puuttuneiden yksikkötestien osalta (H13).
  • tekoälymallin vastauksen tyyppi ja finishReason="STOP" pakotettu ennen tiivistyksen hyväksymistä (T1).
v1.2.1

Analyysiraportin korjaukset (Erät 1–4):

  • XSS-sanitointi: markdownin renderöinti puhdistaa HTML-koodin haittakehyksiltä (K1).
  • Retraktoitujen julkaisujen suodatus lisätty hakuun (K2).
  • Tiivistyksen luotettavuus: finishReason tarkistus tekoälymallin vastauksessa (T1).
  • Deduplikointi ei luota enää hakuvaiheen osumalohkoihin. Tuplien siivous yhdistetty ja viedään loppuun asti (T2).
  • Merkkisuoja: changelogin päivitys estää merkintöjen poiston epävalidilla syötteellä (T3).
  • HTML/URL-escapaus: linkkien osoitteet renderöidään turvallisesti (T4).
  • Ylimääräinen articles.json-vientikopio poistettu, JSON:in lataus vain data/-kansion katsaushakemistosta (T5).
  • Workflow-timeoutit: GitHub Actionsiin lisätty aikarajat jumiutuvien ajojen varalta (T6).
  • Lokirivin päätepisteoletus korjattu genai:ksi (H1); viikkoajon COUNT-arvon numeerisuus validoidaan (H2).
  • macOS-yhteensopivuus viikon numeron laskentaan bash-skriptissä (H3).
  • Alustariippumaton strftime-korvaus render.py:ssä (H4).
  • DOI-täsmäykseen sanarajojen tarkistus regexillä täydellisyysraportointia varten (H5).
  • RSS-syöte (feed.xml) on nyt 100% standardin mukainen (H6).
  • Tietoturva/kestävyys: GitHub Actions -toiminnot kiinnitetty SHA-tiivisteillä (H7).
  • Prompt-injektiosuoja lisätty AI-tiivistysohjeisiin CLI- ja CI-polkuja varten (H11).
  • Turvaotsakkeet (CSP, nosniff, Referrer-Policy) lisätty vercel.json-konfiguraatioon (H12).
  • Työkalujen laatu- ja tyyppitarkistukset: Ruff ja Mypy lisätty CI-putkeen, tyyppivirheet korjattu (H13).
  • Dependabot konfiguroitu päivittämään riippuvuuksia viikottain (J2).
  • OpenGraph-metatiedot HTML-pohjissa (J4).
v1.2.0

Jatkokehitys analyysiraportin pohjalta:

  • Recall-mittari: haku kirjoittaa data/<pvm>/coverage.json:n (esearch-osumat vs. haetut vs. säilytetyt, pudotukset syyn ja lehden mukaan) ja varoittaa lehdistä, joista putosi enemmän kuin säilyi. Tekee näkymättömästä recall-riskistä mitattavan.
  • RSS 2.0 -syöte (site/feed.xml), generoidaan manifestista; linkit SITE_URL-muuttujasta. Syötelinkki lisätty etu- ja katsaussivujen <head>:iin.
  • Etusivun arkistonavigointi: katsaukset ryhmitellään vuosittain omiin osioihinsa.
  • Uudet yksikkötestit recall-mittarille, RSS-syötteelle ja vuosiryhmittelylle.
  • Siivous (H3): poistettu orpo data/2026-07-18/ (viikko 29:n aiempi, sittemmin 2026-07-19:ään yhdistetty katsaus). Varmistettu ensin, että kaikki sen 39 PMID:iä sisältyvät jo 2026-07-19/articles.json:iin, joten deduplikointihistoria säilyy ehjänä.
v1.1.0

Analyysiraportin (ANALYYSIRAPORTTI.md) pohjalta korjattu löydöksiä:

  • Turvallisuus: heartbeat-ping-URL pois config/journals.json:ista → HEARTBEAT_URL (Actions-salaisuus / plist). API-avain query-parametrista x-goog-api-key-otsikkoon.
  • Luotettavuus: PubMed-haku sivuttaa nyt kaikki osumat (retstart) turvarajaan asti ja varoittaa äänekkäästi katkaisusta (ei enää hiljaista >500 osuman katkaisua).
  • E-utilities-retry erottaa ohimenevät (429/5xx/verkko) pysyvistä (4xx) virheistä.
  • Riippuvuudet kiinnitetty requirements.txt:iin (weasyprint, requests, markdown); workflow ja setup_venv.sh asentavat siitä. Turha GOOGLE_CLOUD_PROJECT_ID poistettu.
  • tiivistysskriptin oletuspäätepiste ja dokumentaatio korjattu genai:ksi (ei Vertex).
  • Lisätty offline-yksikkötestit (tests/) ja testi-CI (.github/workflows/tests.yml).
v1.0.0

Versiointi käyttöön: VERSION (semver) + tämä CHANGELOG.md.

Viikkoajo päivittää "Julkaisut"-osion automaattisesti ja renderöinti leimaa version sivun/PDF:n alatunnisteeseen. - Viikko 29:n kaksi katsausta yhdistetty yhdeksi (2026-07-19). - PDF-fontit: pääotsikko 14 pt, muu teksti 10 pt. - Toisen tekoälypalvelun viittaukset poistettu; tekoälymalli on ainoa dokumentoitu tiivistin, pilviajo (GitHub Actions) oletus. - Tiivistin vaihdettu genai-päätepisteeseen (Vertex ei hyväksy tavallista API-avainta) ja viikkoajo siirretty GitHub Actionsiin.