diff --git a/NTO/Accounting/attributes/commercialPartnerCountryCode.ttl b/NTO/Accounting/attributes/commercialPartnerCountryCode.ttl deleted file mode 100644 index e87628447fa..00000000000 --- a/NTO/Accounting/attributes/commercialPartnerCountryCode.ttl +++ /dev/null @@ -1,14 +0,0 @@ -@prefix ogit.Accounting: . -@prefix ogit: . -@prefix owl: . -@prefix rdfs: . -@prefix dcterms: . - -ogit.Accounting:commercialPartnerCountryCode - a owl:DatatypeProperty; - rdfs:subPropertyOf ogit:Attribute; - rdfs:label "commercialPartnerCountryCode"; - dcterms:description "ISO 3166-1 alpha-2 code of the commercial partner's country. Denormalised root-level promotion of the Odoo 3-hop chain `partner_id.commercial_partner_id.country_id.code` (or `move_id.commercial_partner_id.country_id`). Load-bearing for sanctions cross-reference (OFAC/EU/UN list jurisdiction match), VAT reverse-charge determination, and Peppol/UBL e-invoice routing. Maintained via ComputeRecompute cascade on the parent partner-chain."; - dcterms:valid "start=2026-05-28;"; - dcterms:creator "Claude (AdaWorldAPI/lance-graph 3-hop optim)"; -. diff --git a/NTO/Accounting/attributes/counterpartyCountryCode.ttl b/NTO/Accounting/attributes/counterpartyCountryCode.ttl new file mode 100644 index 00000000000..8f486e6ebfd --- /dev/null +++ b/NTO/Accounting/attributes/counterpartyCountryCode.ttl @@ -0,0 +1,14 @@ +@prefix ogit.Accounting: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Accounting:counterpartyCountryCode + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "counterpartyCountryCode"; + dcterms:description "ISO 3166-1 alpha-2 code of the country of the counterparty (the customer or supplier) of a journal entry. Used to match sanctions-list jurisdictions, to decide VAT reverse charge and to route e-invoices (Peppol/UBL). Kept on the entry so these checks do not have to follow the counterparty relation."; + dcterms:valid "start=2026-05-28;"; + dcterms:creator "Claude (AdaWorldAPI)"; +. diff --git a/NTO/Accounting/attributes/fiscalCountryCode.ttl b/NTO/Accounting/attributes/fiscalCountryCode.ttl index fdf656e23e5..14f495fbcca 100644 --- a/NTO/Accounting/attributes/fiscalCountryCode.ttl +++ b/NTO/Accounting/attributes/fiscalCountryCode.ttl @@ -8,7 +8,7 @@ ogit.Accounting:fiscalCountryCode a owl:DatatypeProperty; rdfs:subPropertyOf ogit:Attribute; rdfs:label "fiscalCountryCode"; - dcterms:description "ISO 3166-1 alpha-2 code of the fiscal country of this entity's company. Denormalised root-level promotion of the Odoo 3-hop chain `company_id.account_fiscal_country_id.code` (5 occurrences across TIER-1 addons per lance-graph EXT-1..6). Collapses VAT calc / sanctions cross-reference / jurisdiction routing reads from O(3) Many2one traversal to O(1) codebook lookup. Recomputed via ComputeRecompute cascade when the entity's company or that company's fiscal country changes."; + dcterms:description "ISO 3166-1 alpha-2 code of the fiscal country of the company that owns this entity. Used for VAT calculation, sanctions checks and jurisdiction routing. Kept on the entity so these checks do not have to follow the company relation; the hasFiscalCountry edge gives the same jurisdiction as a FiscalJurisdiction node."; dcterms:valid "start=2026-05-28;"; - dcterms:creator "Claude (AdaWorldAPI/lance-graph 3-hop optim)"; + dcterms:creator "Claude (AdaWorldAPI)"; . diff --git a/NTO/Accounting/attributes/isEuMember.ttl b/NTO/Accounting/attributes/isEuMember.ttl index 4cd80b47872..04565ac8f49 100644 --- a/NTO/Accounting/attributes/isEuMember.ttl +++ b/NTO/Accounting/attributes/isEuMember.ttl @@ -8,7 +8,7 @@ ogit.Accounting:isEuMember a owl:DatatypeProperty; rdfs:subPropertyOf ogit:Attribute; rdfs:label "isEuMember"; - dcterms:description "Boolean — true iff this fiscal jurisdiction is a current EU member state. Codebook attribute on FiscalJurisdiction. Hot path for VAT reverse-charge eligibility (intra-EU B2B reverse-charge rules), Peppol/UBL e-invoice routing, and MOSS / OSS classification."; + dcterms:description "True if this fiscal jurisdiction is a current EU member state. Used to decide intra-EU reverse charge, Peppol/UBL routing and OSS classification."; dcterms:valid "start=2026-05-28;"; - dcterms:creator "Claude (AdaWorldAPI/lance-graph 3-hop optim)"; + dcterms:creator "Claude (AdaWorldAPI)"; . diff --git a/NTO/Accounting/attributes/iso3166Alpha2.ttl b/NTO/Accounting/attributes/iso3166Alpha2.ttl index 4e94988b2c3..63bb220a6d1 100644 --- a/NTO/Accounting/attributes/iso3166Alpha2.ttl +++ b/NTO/Accounting/attributes/iso3166Alpha2.ttl @@ -8,7 +8,7 @@ ogit.Accounting:iso3166Alpha2 a owl:DatatypeProperty; rdfs:subPropertyOf ogit:Attribute; rdfs:label "iso3166Alpha2"; - dcterms:description "Two-letter ISO 3166-1 alpha-2 country code (e.g. `DE`, `AT`, `CH`, `US`). Canonical codebook key for the FiscalJurisdiction entity — used as the indexed attribute that makes country lookups O(1). The two-letter form is the universal join key across Odoo (`res.country.code`), Peppol/UBL (`ram:CountryCode`), SWIFT BIC country segment, and OFAC sanctions list jurisdiction filters."; + dcterms:description "Two-letter ISO 3166-1 alpha-2 country code (for example `DE`, `AT`, `CH`, `US`). The indexed key of FiscalJurisdiction: one jurisdiction per code. The same two-letter code is used by Peppol/UBL, by the country segment of a SWIFT BIC and by sanctions-list jurisdiction filters."; dcterms:valid "start=2026-05-28;"; - dcterms:creator "Claude (AdaWorldAPI/lance-graph 3-hop optim)"; + dcterms:creator "Claude (AdaWorldAPI)"; . diff --git a/NTO/Accounting/attributes/productCategoryComplete.ttl b/NTO/Accounting/attributes/productCategoryComplete.ttl deleted file mode 100644 index 60dc51dc72a..00000000000 --- a/NTO/Accounting/attributes/productCategoryComplete.ttl +++ /dev/null @@ -1,14 +0,0 @@ -@prefix ogit.Accounting: . -@prefix ogit: . -@prefix owl: . -@prefix rdfs: . -@prefix dcterms: . - -ogit.Accounting:productCategoryComplete - a owl:DatatypeProperty; - rdfs:subPropertyOf ogit:Attribute; - rdfs:label "productCategoryComplete"; - dcterms:description "Slash-separated full category path of a product (e.g. `All / Saleable / Goods`). Denormalised root-level promotion of the Odoo 3-hop chain `product_id.product_tmpl_id.categ_id.complete_name` (and the 2-hop variant `product_id.categ_id.complete_name`). Hot path for tax-category routing, MwSt-Satz selection (SKR03/SKR04 mapping), and inventory cost-method dispatch. Recomputed on product template or category change."; - dcterms:valid "start=2026-05-28;"; - dcterms:creator "Claude (AdaWorldAPI/lance-graph 3-hop optim)"; -. diff --git a/NTO/Accounting/attributes/vatRateStandard.ttl b/NTO/Accounting/attributes/vatRateStandard.ttl index 3f7015bd325..68755263a34 100644 --- a/NTO/Accounting/attributes/vatRateStandard.ttl +++ b/NTO/Accounting/attributes/vatRateStandard.ttl @@ -8,7 +8,7 @@ ogit.Accounting:vatRateStandard a owl:DatatypeProperty; rdfs:subPropertyOf ogit:Attribute; rdfs:label "vatRateStandard"; - dcterms:description "Standard VAT / Mehrwertsteuer rate of this fiscal jurisdiction, expressed as a decimal fraction (e.g. `0.19` for the German 19% Regelsteuersatz). Codebook attribute on FiscalJurisdiction. Reduced rates and special exemptions are not modelled here — use the per-jurisdiction Tax entity for those."; + dcterms:description "Standard VAT rate of this fiscal jurisdiction as a decimal fraction (for example `0.19` for 19%). Reduced rates and exemptions are not modelled here; use the jurisdiction's Tax entity for those."; dcterms:valid "start=2026-05-28;"; - dcterms:creator "Claude (AdaWorldAPI/lance-graph 3-hop optim)"; + dcterms:creator "Claude (AdaWorldAPI)"; . diff --git a/NTO/Accounting/entities/FiscalJurisdiction.ttl b/NTO/Accounting/entities/FiscalJurisdiction.ttl index 87a8c1448ad..b68caea42c9 100644 --- a/NTO/Accounting/entities/FiscalJurisdiction.ttl +++ b/NTO/Accounting/entities/FiscalJurisdiction.ttl @@ -1,5 +1,7 @@ @prefix ogit.Accounting: . @prefix ogit: . +@prefix ogit.Compliance: . +@prefix ogit.FinancialMarket: . @prefix rdfs: . @prefix dcterms: . @@ -7,9 +9,9 @@ ogit.Accounting:FiscalJurisdiction a rdfs:Class; rdfs:subClassOf ogit:Entity; rdfs:label "FiscalJurisdiction"; - dcterms:description "Codebook entry pinning a fiscal jurisdiction by ISO 3166-1 alpha-2 code. The canonical lookup target for `hasFiscalCountry` edges and the resolved leaf of the Odoo 3-hop chain `entity.company_id.account_fiscal_country_id`. Strict codebook semantics: one entry per ISO 3166-1 alpha-2 code, no duplicates. Standard VAT rate + EU membership are denormalised onto the entry so jurisdiction-conditional logic (reverse-charge, MOSS/OSS, GoBD applicability) reads as O(1) attribute access, not a downstream graph join. Cross-jurisdictional sanctions cross-reference is supported via an outbound edge to ogit.Compliance:SanctionsEntry when the jurisdiction maintains a sanctions programme."; + dcterms:description "A fiscal jurisdiction, identified by its ISO 3166-1 alpha-2 code: one entry per code. The target of hasFiscalCountry edges. Carries the standard VAT rate and EU membership, so jurisdiction-dependent rules (reverse charge, OSS, record-keeping obligations) read an attribute instead of following further edges. Links to a sanctions entry when the jurisdiction runs a sanctions programme."; dcterms:valid "start=2026-05-28;"; - dcterms:creator "Claude (AdaWorldAPI/lance-graph 3-hop optim)"; + dcterms:creator "Claude (AdaWorldAPI)"; ogit:scope "NTO"; ogit:parent ogit:Node; ogit:mandatory-attributes ( diff --git a/NTO/Accounting/entities/JournalEntry.ttl b/NTO/Accounting/entities/JournalEntry.ttl index 80d0600e571..17c697d6c12 100644 --- a/NTO/Accounting/entities/JournalEntry.ttl +++ b/NTO/Accounting/entities/JournalEntry.ttl @@ -8,9 +8,9 @@ ogit.Accounting:JournalEntry a rdfs:Class; rdfs:subClassOf ogit:Entity; rdfs:label "JournalEntry"; - dcterms:description "A double-entry accounting journal entry — the canonical OGIT projection of Odoo `account.move` (and the deeply analogous SAP `BSEG` document line, MS Dynamics `JournalLine`, DATEV `Buchung`). Demonstrates the 3-hop optimisation doctrine: instead of the lookups `entity.company_id.account_fiscal_country_id.code` (5+ Odoo sites) and `move.commercial_partner_id.country_id`, the leaf attributes are promoted onto JournalEntry directly (`fiscalCountryCode`, `commercialPartnerCountryCode`) and the intermediate hops are also accessible via the shortcut verb `hasFiscalCountry` for graph traversal. Both routes are kept — promoted attribute for hot-path reads, edge verb for cascade traversal — and the EdgeColumn ComputeRecompute cascade keeps them coherent."; + dcterms:description "A double-entry accounting journal entry: a dated, balanced set of debit and credit lines in one currency, with a status (for example draft or posted). The fiscal country of the owning company and the country of the counterparty are kept on the entry (fiscalCountryCode, counterpartyCountryCode) for VAT and sanctions checks; the hasFiscalCountry edge links the same jurisdiction as a node."; dcterms:valid "start=2026-05-28;"; - dcterms:creator "Claude (AdaWorldAPI/lance-graph 3-hop optim)"; + dcterms:creator "Claude (AdaWorldAPI)"; ogit:scope "NTO"; ogit:parent ogit:Node; ogit:mandatory-attributes ( @@ -25,13 +25,13 @@ ogit.Accounting:JournalEntry ogit:createdAt ogit:lastUpdatedAt ogit.Accounting:fiscalCountryCode - ogit.Accounting:commercialPartnerCountryCode + ogit.Accounting:counterpartyCountryCode ); ogit:indexed-attributes ( ogit:id ogit:status ogit.Accounting:fiscalCountryCode - ogit.Accounting:commercialPartnerCountryCode + ogit.Accounting:counterpartyCountryCode ); ogit:allowed ( [ ogit.Accounting:hasFiscalCountry ogit.Accounting:FiscalJurisdiction ] diff --git a/NTO/Accounting/verbs/hasFiscalCountry.ttl b/NTO/Accounting/verbs/hasFiscalCountry.ttl index b03a305bbb3..82a28625c96 100644 --- a/NTO/Accounting/verbs/hasFiscalCountry.ttl +++ b/NTO/Accounting/verbs/hasFiscalCountry.ttl @@ -8,7 +8,7 @@ ogit.Accounting:hasFiscalCountry a owl:ObjectProperty; rdfs:subPropertyOf ogit:Verb; rdfs:label "hasFiscalCountry"; - dcterms:description "Direct edge from a fiscal entity (JournalEntry, CompanyEntity, FinancialInstrument, Tax) to its fiscal jurisdiction (FiscalJurisdiction codebook entry / FinancialMarket:Country). Collapses the Odoo 2-hop chain `entity.company_id.account_fiscal_country_id` and the 3-hop variant `entity.company_id.account_fiscal_country_id.code` into one typed edge in the EdgeColumn. Targets: ogit.Accounting:FiscalJurisdiction (preferred) or ogit.FinancialMarket:Country."; + dcterms:description "Links a fiscal entity (journal entry, company, financial instrument, tax) to the jurisdiction whose tax rules apply to it. Target: ogit.Accounting:FiscalJurisdiction, or ogit.FinancialMarket:Country."; dcterms:valid "start=2026-05-28;"; - dcterms:creator "Claude (AdaWorldAPI/lance-graph 3-hop optim)"; + dcterms:creator "Claude (AdaWorldAPI)"; . diff --git a/NTO/Accounting/verbs/hasPickingType.ttl b/NTO/Accounting/verbs/hasPickingType.ttl deleted file mode 100644 index 5968c905e9c..00000000000 --- a/NTO/Accounting/verbs/hasPickingType.ttl +++ /dev/null @@ -1,14 +0,0 @@ -@prefix ogit.Accounting: . -@prefix ogit: . -@prefix owl: . -@prefix rdfs: . -@prefix dcterms: . - -ogit.Accounting:hasPickingType - a owl:ObjectProperty; - rdfs:subPropertyOf ogit:Verb; - rdfs:label "hasPickingType"; - dcterms:description "Direct edge from a stock move / move line to its picking-type configuration. Collapses the Odoo 3-hop chain `picking_id.picking_type_id.` into one typed edge that the EdgeColumn cascade can follow without intermediate hops. Used by warehouse routing + inventory-event dispatch."; - dcterms:valid "start=2026-05-28;"; - dcterms:creator "Claude (AdaWorldAPI/lance-graph 3-hop optim)"; -. diff --git a/NTO/Accounting/verbs/hasProductCategory.ttl b/NTO/Accounting/verbs/hasProductCategory.ttl deleted file mode 100644 index c3cbb1e7455..00000000000 --- a/NTO/Accounting/verbs/hasProductCategory.ttl +++ /dev/null @@ -1,14 +0,0 @@ -@prefix ogit.Accounting: . -@prefix ogit: . -@prefix owl: . -@prefix rdfs: . -@prefix dcterms: . - -ogit.Accounting:hasProductCategory - a owl:ObjectProperty; - rdfs:subPropertyOf ogit:Verb; - rdfs:label "hasProductCategory"; - dcterms:description "Direct edge from a line item / stock move / sale order line to its product category. Collapses the Odoo 3-hop chain `line.product_id.product_tmpl_id.categ_id` (and the 2-hop variant on simpler models) into one typed edge. Hot path for tax-mapping (SKR03 vs SKR04 routing), inventory valuation method dispatch, and reporting aggregation."; - dcterms:valid "start=2026-05-28;"; - dcterms:creator "Claude (AdaWorldAPI/lance-graph 3-hop optim)"; -. diff --git a/NTO/Healthcare/attributes/anomize.ttl b/NTO/Healthcare/attributes/anomize.ttl new file mode 100644 index 00000000000..00adc7f4a56 --- /dev/null +++ b/NTO/Healthcare/attributes/anomize.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:anomize + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "anomize"; + dcterms:description "Anonymisation flag for research export (praxis_patient.p_anomize)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_anomize"; + ogit:type "xsd:boolean"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/birth.ttl b/NTO/Healthcare/attributes/birth.ttl new file mode 100644 index 00000000000..0debfab9129 --- /dev/null +++ b/NTO/Healthcare/attributes/birth.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:birth + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "birth"; + dcterms:description "Patient date of birth (praxis_patient.p_birth)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_birth"; + ogit:type "xsd:dateTime"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/bloodPressureDiastolic.ttl b/NTO/Healthcare/attributes/bloodPressureDiastolic.ttl new file mode 100644 index 00000000000..29af6427145 --- /dev/null +++ b/NTO/Healthcare/attributes/bloodPressureDiastolic.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:bloodPressureDiastolic + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "bloodPressureDiastolic"; + dcterms:description "Diastolic blood pressure in mmHg (pf_vital_bloodpressure.dia)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_vital_bloodpressure.dia"; + ogit:type "xsd:float"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/bloodPressureSystolic.ttl b/NTO/Healthcare/attributes/bloodPressureSystolic.ttl new file mode 100644 index 00000000000..429f3ec3630 --- /dev/null +++ b/NTO/Healthcare/attributes/bloodPressureSystolic.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:bloodPressureSystolic + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "bloodPressureSystolic"; + dcterms:description "Systolic blood pressure in mmHg (pf_vital_bloodpressure.sys)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_vital_bloodpressure.sys"; + ogit:type "xsd:float"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/country.ttl b/NTO/Healthcare/attributes/country.ttl new file mode 100644 index 00000000000..c7daf9a1163 --- /dev/null +++ b/NTO/Healthcare/attributes/country.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:country + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "country"; + dcterms:description "Patient country (praxis_patient.p_country)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_country"; + ogit:type "xsd:string"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/createDate.ttl b/NTO/Healthcare/attributes/createDate.ttl new file mode 100644 index 00000000000..4c512f440da --- /dev/null +++ b/NTO/Healthcare/attributes/createDate.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:createDate + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "createDate"; + dcterms:description "Entry creation timestamp (pf_diagnosis.d_createdate)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_diagnosis.d_createdate"; + ogit:type "xsd:dateTime"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/datum.ttl b/NTO/Healthcare/attributes/datum.ttl new file mode 100644 index 00000000000..4095837529e --- /dev/null +++ b/NTO/Healthcare/attributes/datum.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:datum + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "datum"; + dcterms:description "Measurement timestamp (pf_vital_bloodpressure.datum)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_vital_bloodpressure.datum"; + ogit:type "xsd:dateTime"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/dbSourceDb.ttl b/NTO/Healthcare/attributes/dbSourceDb.ttl new file mode 100644 index 00000000000..193aeb445de --- /dev/null +++ b/NTO/Healthcare/attributes/dbSourceDb.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:dbSourceDb + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "dbSourceDb"; + dcterms:description "Provenance: name of the source database the row was imported from (pf_therapy.db_sourcedb)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_therapy.db_sourcedb"; + ogit:type "xsd:string"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/dbSourceId.ttl b/NTO/Healthcare/attributes/dbSourceId.ttl new file mode 100644 index 00000000000..6e3990c0967 --- /dev/null +++ b/NTO/Healthcare/attributes/dbSourceId.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:dbSourceId + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "dbSourceId"; + dcterms:description "Provenance: row id in the source database (pf_therapy.db_sourceid)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_therapy.db_sourceid"; + ogit:type "xsd:integer"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/dbSpezId.ttl b/NTO/Healthcare/attributes/dbSpezId.ttl new file mode 100644 index 00000000000..5b325966c30 --- /dev/null +++ b/NTO/Healthcare/attributes/dbSpezId.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:dbSpezId + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "dbSpezId"; + dcterms:description "Specialty FK -> combo_spez.id (pf_diagnosis.db_spezid)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_diagnosis.db_spezid"; + ogit:type "xsd:integer"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/ethnic.ttl b/NTO/Healthcare/attributes/ethnic.ttl new file mode 100644 index 00000000000..5547b08f0dd --- /dev/null +++ b/NTO/Healthcare/attributes/ethnic.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:ethnic + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "ethnic"; + dcterms:description "Self-declared ethnicity (praxis_patient.p_ethnic)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_ethnic"; + ogit:type "xsd:string"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/fileDate.ttl b/NTO/Healthcare/attributes/fileDate.ttl new file mode 100644 index 00000000000..2670476671a --- /dev/null +++ b/NTO/Healthcare/attributes/fileDate.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:fileDate + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "fileDate"; + dcterms:description "Clinical event date the diagnosis refers to (pf_diagnosis.file_date)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_diagnosis.file_date"; + ogit:type "xsd:dateTime"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/fileDiag.ttl b/NTO/Healthcare/attributes/fileDiag.ttl new file mode 100644 index 00000000000..1b92d585755 --- /dev/null +++ b/NTO/Healthcare/attributes/fileDiag.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:fileDiag + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "fileDiag"; + dcterms:description "Short diagnosis label (pf_diagnosis.file_diag)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_diagnosis.file_diag"; + ogit:type "xsd:string"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/fileIcd.ttl b/NTO/Healthcare/attributes/fileIcd.ttl new file mode 100644 index 00000000000..bdfa1121165 --- /dev/null +++ b/NTO/Healthcare/attributes/fileIcd.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:fileIcd + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "fileIcd"; + dcterms:description "ICD-10 code for the diagnosis (pf_diagnosis.file_icd)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_diagnosis.file_icd"; + ogit:type "xsd:string"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/fileKd.ttl b/NTO/Healthcare/attributes/fileKd.ttl new file mode 100644 index 00000000000..5c2738745d6 --- /dev/null +++ b/NTO/Healthcare/attributes/fileKd.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:fileKd + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "fileKd"; + dcterms:description "Chronic-diagnosis flag (pf_diagnosis.file_kd)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_diagnosis.file_kd"; + ogit:type "xsd:boolean"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/fileKeyword.ttl b/NTO/Healthcare/attributes/fileKeyword.ttl new file mode 100644 index 00000000000..1f931255c5c --- /dev/null +++ b/NTO/Healthcare/attributes/fileKeyword.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:fileKeyword + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "fileKeyword"; + dcterms:description "Therapy keyword / short label (pf_therapy.file_keyword)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_therapy.file_keyword"; + ogit:type "xsd:string"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/fileLd.ttl b/NTO/Healthcare/attributes/fileLd.ttl new file mode 100644 index 00000000000..f8a5fc2ea28 --- /dev/null +++ b/NTO/Healthcare/attributes/fileLd.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:fileLd + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "fileLd"; + dcterms:description "Leading-diagnosis flag (pf_diagnosis.file_ld)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_diagnosis.file_ld"; + ogit:type "xsd:boolean"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/fileMedicineDose.ttl b/NTO/Healthcare/attributes/fileMedicineDose.ttl new file mode 100644 index 00000000000..d44a17ec9e0 --- /dev/null +++ b/NTO/Healthcare/attributes/fileMedicineDose.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:fileMedicineDose + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "fileMedicineDose"; + dcterms:description "Numeric per-administration dose (pat_medication.file_medicine_dose)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pat_medication.file_medicine_dose"; + ogit:type "xsd:float"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/fileMedicineName.ttl b/NTO/Healthcare/attributes/fileMedicineName.ttl new file mode 100644 index 00000000000..fc2b312002e --- /dev/null +++ b/NTO/Healthcare/attributes/fileMedicineName.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:fileMedicineName + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "fileMedicineName"; + dcterms:description "Trade / display name of the medicine (pat_medication.file_medicine_name)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pat_medication.file_medicine_name"; + ogit:type "xsd:string"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/fileMedicineTyp.ttl b/NTO/Healthcare/attributes/fileMedicineTyp.ttl new file mode 100644 index 00000000000..fddf5937e67 --- /dev/null +++ b/NTO/Healthcare/attributes/fileMedicineTyp.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:fileMedicineTyp + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "fileMedicineTyp"; + dcterms:description "Medicine type FK -> combo_medication_typ.id (pat_medication.file_medicine_typ)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pat_medication.file_medicine_typ"; + ogit:type "xsd:integer"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/fileNotice.ttl b/NTO/Healthcare/attributes/fileNotice.ttl new file mode 100644 index 00000000000..18debb06d92 --- /dev/null +++ b/NTO/Healthcare/attributes/fileNotice.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:fileNotice + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "fileNotice"; + dcterms:description "Free-text notice attached to the prescription (pat_medication.file_notice)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pat_medication.file_notice"; + ogit:type "xsd:string"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/filePraepId.ttl b/NTO/Healthcare/attributes/filePraepId.ttl new file mode 100644 index 00000000000..dae56d197fb --- /dev/null +++ b/NTO/Healthcare/attributes/filePraepId.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:filePraepId + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "filePraepId"; + dcterms:description "Drug-product identifier in the medicines master (pat_medication.file_praepid)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pat_medication.file_praepid"; + ogit:type "xsd:string"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/fileStart.ttl b/NTO/Healthcare/attributes/fileStart.ttl new file mode 100644 index 00000000000..0fadca133f3 --- /dev/null +++ b/NTO/Healthcare/attributes/fileStart.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:fileStart + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "fileStart"; + dcterms:description "Treatment start date (pat_medication.file_start)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pat_medication.file_start"; + ogit:type "xsd:dateTime"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/fileStop.ttl b/NTO/Healthcare/attributes/fileStop.ttl new file mode 100644 index 00000000000..de1bfafabb5 --- /dev/null +++ b/NTO/Healthcare/attributes/fileStop.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:fileStop + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "fileStop"; + dcterms:description "Treatment stop date (pat_medication.file_stop)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pat_medication.file_stop"; + ogit:type "xsd:dateTime"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/fileText.ttl b/NTO/Healthcare/attributes/fileText.ttl new file mode 100644 index 00000000000..89b483cb456 --- /dev/null +++ b/NTO/Healthcare/attributes/fileText.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:fileText + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "fileText"; + dcterms:description "Free-text clinical description (pf_diagnosis.file_Text)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_diagnosis.file_Text"; + ogit:type "xsd:string"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/findingSummary.ttl b/NTO/Healthcare/attributes/findingSummary.ttl new file mode 100644 index 00000000000..a082f606e99 --- /dev/null +++ b/NTO/Healthcare/attributes/findingSummary.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:findingSummary + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "findingSummary"; + dcterms:description "Free-text or coded summary of the assessment's finding. Structured per-site findings are cascade-path nodes part_of the assessment, not a codebook slot."; + dcterms:source "SNOMED-CT:404684003"; + ogit:type "xsd:string"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/firstname.ttl b/NTO/Healthcare/attributes/firstname.ttl new file mode 100644 index 00000000000..050d64f5db4 --- /dev/null +++ b/NTO/Healthcare/attributes/firstname.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:firstname + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "firstname"; + dcterms:description "Patient given name (praxis_patient.p_firstname)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_firstname"; + ogit:type "xsd:string"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/labId.ttl b/NTO/Healthcare/attributes/labId.ttl new file mode 100644 index 00000000000..30255bd9116 --- /dev/null +++ b/NTO/Healthcare/attributes/labId.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:labId + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "labId"; + dcterms:description "Laboratory FK / partner identifier (pf_laboratory_values.labid)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_laboratory_values.labid"; + ogit:type "xsd:integer"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/lastname.ttl b/NTO/Healthcare/attributes/lastname.ttl new file mode 100644 index 00000000000..6f1bbc7f9b5 --- /dev/null +++ b/NTO/Healthcare/attributes/lastname.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:lastname + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "lastname"; + dcterms:description "Patient family name (praxis_patient.p_lastname)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_lastname"; + ogit:type "xsd:string"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/loginAt.ttl b/NTO/Healthcare/attributes/loginAt.ttl new file mode 100644 index 00000000000..989f3fdaa28 --- /dev/null +++ b/NTO/Healthcare/attributes/loginAt.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:loginAt + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "loginAt"; + dcterms:description "Patient login time into waiting room (praxis_patient_waitingroom.p_login)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient_waitingroom.p_login"; + ogit:type "xsd:dateTime"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/logoutAt.ttl b/NTO/Healthcare/attributes/logoutAt.ttl new file mode 100644 index 00000000000..3691b40b567 --- /dev/null +++ b/NTO/Healthcare/attributes/logoutAt.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:logoutAt + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "logoutAt"; + dcterms:description "Patient logout / visit-end time (praxis_patient_waitingroom.p_logout)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient_waitingroom.p_logout"; + ogit:type "xsd:dateTime"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/loincId.ttl b/NTO/Healthcare/attributes/loincId.ttl new file mode 100644 index 00000000000..fcb4cf044ca --- /dev/null +++ b/NTO/Healthcare/attributes/loincId.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:loincId + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "loincId"; + dcterms:description "LOINC reference code identifier (pf_laboratory_values.loincid)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_laboratory_values.loincid"; + ogit:type "xsd:integer"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/loincRef.ttl b/NTO/Healthcare/attributes/loincRef.ttl new file mode 100644 index 00000000000..924af7a4717 --- /dev/null +++ b/NTO/Healthcare/attributes/loincRef.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:loincRef + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "loincRef"; + dcterms:description "Out-of-namespace reference to LOINC concept. Carried as a property reference rather than an in-namespace entity since LOINC is governed externally."; + dcterms:source "loinc:"; + ogit:type "xsd:anyURI"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/mail.ttl b/NTO/Healthcare/attributes/mail.ttl new file mode 100644 index 00000000000..3d52b82a2a1 --- /dev/null +++ b/NTO/Healthcare/attributes/mail.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:mail + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "mail"; + dcterms:description "Patient email (praxis_patient.p_mail)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_mail"; + ogit:type "xsd:string"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/medicationDailyDose.ttl b/NTO/Healthcare/attributes/medicationDailyDose.ttl new file mode 100644 index 00000000000..4e48432e99e --- /dev/null +++ b/NTO/Healthcare/attributes/medicationDailyDose.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:medicationDailyDose + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "medicationDailyDose"; + dcterms:description "Daily-dose FK -> combo_medication_dailydose.id (pat_medication.pf_medication_dailydose)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pat_medication.pf_medication_dailydose"; + ogit:type "xsd:integer"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/medicationDoseUnit.ttl b/NTO/Healthcare/attributes/medicationDoseUnit.ttl new file mode 100644 index 00000000000..8df7c3d3a61 --- /dev/null +++ b/NTO/Healthcare/attributes/medicationDoseUnit.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:medicationDoseUnit + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "medicationDoseUnit"; + dcterms:description "Dose-unit FK -> combo_medication_dose_unit.id (pat_medication.pf_medication_dose_unit)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pat_medication.pf_medication_dose_unit"; + ogit:type "xsd:integer"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/medicationInterval.ttl b/NTO/Healthcare/attributes/medicationInterval.ttl new file mode 100644 index 00000000000..451478113fd --- /dev/null +++ b/NTO/Healthcare/attributes/medicationInterval.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:medicationInterval + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "medicationInterval"; + dcterms:description "Interval-of-administration FK -> combo_medication_interval.id (pat_medication.pf_medication_interval)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pat_medication.pf_medication_interval"; + ogit:type "xsd:integer"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/medicationStopReason.ttl b/NTO/Healthcare/attributes/medicationStopReason.ttl new file mode 100644 index 00000000000..662771fe1f2 --- /dev/null +++ b/NTO/Healthcare/attributes/medicationStopReason.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:medicationStopReason + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "medicationStopReason"; + dcterms:description "Stop-reason FK -> combo_medication_stop_reason.id (pat_medication.pf_medication_stop_reason)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pat_medication.pf_medication_stop_reason"; + ogit:type "xsd:integer"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/mobil.ttl b/NTO/Healthcare/attributes/mobil.ttl new file mode 100644 index 00000000000..01ed0b99d3d --- /dev/null +++ b/NTO/Healthcare/attributes/mobil.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:mobil + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "mobil"; + dcterms:description "Patient mobile phone (praxis_patient.p_mobil)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_mobil"; + ogit:type "xsd:string"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/noticeForDoc.ttl b/NTO/Healthcare/attributes/noticeForDoc.ttl new file mode 100644 index 00000000000..0697a4cacb3 --- /dev/null +++ b/NTO/Healthcare/attributes/noticeForDoc.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:noticeForDoc + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "noticeForDoc"; + dcterms:description "Free-text reception notice for the doctor (praxis_patient_waitingroom.p_notice_for_doc)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient_waitingroom.p_notice_for_doc"; + ogit:type "xsd:string"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/performedAt.ttl b/NTO/Healthcare/attributes/performedAt.ttl new file mode 100644 index 00000000000..f41584be3c2 --- /dev/null +++ b/NTO/Healthcare/attributes/performedAt.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:performedAt + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "performedAt"; + dcterms:description "Timestamp at which the assessment was performed."; + dcterms:source "SNOMED-CT:386053000"; + ogit:type "xsd:dateTime"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/place.ttl b/NTO/Healthcare/attributes/place.ttl new file mode 100644 index 00000000000..956f36c9e97 --- /dev/null +++ b/NTO/Healthcare/attributes/place.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:place + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "place"; + dcterms:description "City / place of residence (praxis_patient.p_place)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_place"; + ogit:type "xsd:string"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/priority.ttl b/NTO/Healthcare/attributes/priority.ttl new file mode 100644 index 00000000000..b3392fd8d58 --- /dev/null +++ b/NTO/Healthcare/attributes/priority.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:priority + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "priority"; + dcterms:description "Visit triage priority (praxis_patient_waitingroom.p_priority)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient_waitingroom.p_priority"; + ogit:type "xsd:integer"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/pulse.ttl b/NTO/Healthcare/attributes/pulse.ttl new file mode 100644 index 00000000000..9e46ec19031 --- /dev/null +++ b/NTO/Healthcare/attributes/pulse.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:pulse + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "pulse"; + dcterms:description "Pulse rate (bpm) (pf_vital_bloodpressure.pulse)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_vital_bloodpressure.pulse"; + ogit:type "xsd:float"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/sexd.ttl b/NTO/Healthcare/attributes/sexd.ttl new file mode 100644 index 00000000000..6fc20bc2b7f --- /dev/null +++ b/NTO/Healthcare/attributes/sexd.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:sexd + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "sexd"; + dcterms:description "Boolean: patient identifies as diverse (praxis_patient.p_sexd)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_sexd"; + ogit:type "xsd:boolean"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/sexf.ttl b/NTO/Healthcare/attributes/sexf.ttl new file mode 100644 index 00000000000..4daae3da27a --- /dev/null +++ b/NTO/Healthcare/attributes/sexf.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:sexf + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "sexf"; + dcterms:description "Boolean: patient identifies as female (praxis_patient.p_sexf)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_sexf"; + ogit:type "xsd:boolean"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/sexm.ttl b/NTO/Healthcare/attributes/sexm.ttl new file mode 100644 index 00000000000..2235b16dbac --- /dev/null +++ b/NTO/Healthcare/attributes/sexm.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:sexm + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "sexm"; + dcterms:description "Boolean: patient identifies as male (praxis_patient.p_sexm)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_sexm"; + ogit:type "xsd:boolean"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/spez.ttl b/NTO/Healthcare/attributes/spez.ttl new file mode 100644 index 00000000000..3c1ea318c1b --- /dev/null +++ b/NTO/Healthcare/attributes/spez.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:spez + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "spez"; + dcterms:description "Specialty / profile assigned to the patient (praxis_patient.p_spez)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_spez"; + ogit:type "xsd:string"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/street.ttl b/NTO/Healthcare/attributes/street.ttl new file mode 100644 index 00000000000..ce5619d42c8 --- /dev/null +++ b/NTO/Healthcare/attributes/street.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:street + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "street"; + dcterms:description "Street address (praxis_patient.p_street)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_street"; + ogit:type "xsd:string"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/timeInWaiting.ttl b/NTO/Healthcare/attributes/timeInWaiting.ttl new file mode 100644 index 00000000000..ea71d5163eb --- /dev/null +++ b/NTO/Healthcare/attributes/timeInWaiting.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:timeInWaiting + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "timeInWaiting"; + dcterms:description "Computed waiting duration in minutes (praxis_patient_waitingroom.p_time_in_waiting)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient_waitingroom.p_time_in_waiting"; + ogit:type "xsd:integer"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/title1.ttl b/NTO/Healthcare/attributes/title1.ttl new file mode 100644 index 00000000000..3441dcef8d5 --- /dev/null +++ b/NTO/Healthcare/attributes/title1.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:title1 + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "title1"; + dcterms:description "Honorific title (praxis_patient.p_title1)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_title1"; + ogit:type "xsd:string"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/title2.ttl b/NTO/Healthcare/attributes/title2.ttl new file mode 100644 index 00000000000..a206b20b053 --- /dev/null +++ b/NTO/Healthcare/attributes/title2.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:title2 + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "title2"; + dcterms:description "Academic title (praxis_patient.p_title2)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_title2"; + ogit:type "xsd:string"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/toDocId.ttl b/NTO/Healthcare/attributes/toDocId.ttl new file mode 100644 index 00000000000..a8d304ad2c2 --- /dev/null +++ b/NTO/Healthcare/attributes/toDocId.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:toDocId + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "toDocId"; + dcterms:description "Doctor assigned to handle the visit (praxis_patient_waitingroom.p_to_doc_id)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient_waitingroom.p_to_doc_id"; + ogit:type "xsd:integer"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/waitingroomId.ttl b/NTO/Healthcare/attributes/waitingroomId.ttl new file mode 100644 index 00000000000..50293cfa047 --- /dev/null +++ b/NTO/Healthcare/attributes/waitingroomId.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:waitingroomId + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "waitingroomId"; + dcterms:description "FK to praxis_waitingroom (praxis_patient_waitingroom.praxis_waitingroomid)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient_waitingroom.praxis_waitingroomid"; + ogit:type "xsd:integer"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/werte.ttl b/NTO/Healthcare/attributes/werte.ttl new file mode 100644 index 00000000000..7e453ef0b24 --- /dev/null +++ b/NTO/Healthcare/attributes/werte.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:werte + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "werte"; + dcterms:description "Numeric measured value (pf_laboratory_values.werte)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_laboratory_values.werte"; + ogit:type "xsd:float"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/attributes/zip.ttl b/NTO/Healthcare/attributes/zip.ttl new file mode 100644 index 00000000000..10ce6b157c8 --- /dev/null +++ b/NTO/Healthcare/attributes/zip.ttl @@ -0,0 +1,16 @@ +@prefix ogit.Healthcare: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:zip + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "zip"; + dcterms:description "Postal code (praxis_patient.p_zip)."; + dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_zip"; + ogit:type "xsd:string"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/Healthcare/entities/AnatomicalStructure.ttl b/NTO/Healthcare/entities/AnatomicalStructure.ttl new file mode 100644 index 00000000000..c530dcf1e07 --- /dev/null +++ b/NTO/Healthcare/entities/AnatomicalStructure.ttl @@ -0,0 +1,26 @@ +@prefix ogit: . +@prefix ogit.Healthcare: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:AnatomicalStructure + a rdfs:Class; + rdfs:subClassOf ogit:Entity; + rdfs:label "AnatomicalStructure"; + dcterms:valid "start=2026-10-10;"; + dcterms:description "A part of the body that an assessment evaluates, such as an organ or a joint. Identified by an external anatomy reference."; + dcterms:creator "claude-ogit-bootstrap"; + ogit:scope "NTO"; + ogit:parent ogit:Node; + ogit:mandatory-attributes ( + ogit:id + ogit:name + ); + ogit:optional-attributes ( + ); + ogit:indexed-attributes ( + ogit:name + ); + ogit:allowed ( + ); +. diff --git a/NTO/Healthcare/entities/Assessment.ttl b/NTO/Healthcare/entities/Assessment.ttl index 28dcdbc7c44..03343313ff7 100644 --- a/NTO/Healthcare/entities/Assessment.ttl +++ b/NTO/Healthcare/entities/Assessment.ttl @@ -19,6 +19,7 @@ ogit.Healthcare:Assessment a rdfs:Class; rdfs:subClassOf ogit:Entity; rdfs:label "Assessment"; + dcterms:valid "start=2026-10-10;"; dcterms:description "A diagnostic evaluation act that produces findings about an anatomical structure. Modality-agnostic root: an assessment is performed by some modality (its is_a subclass) and evaluates some anatomical structure (its part_of grounding)."; dcterms:source "SNOMED-CT:386053000"; dcterms:creator "claude-ogit-bootstrap"; @@ -35,67 +36,7 @@ ogit.Healthcare:Assessment ogit.Healthcare:performedAt ); ogit:allowed ( - [ ogit:assesses ogit.Healthcare:AnatomicalStructure ] + [ ogit.Healthcare:assesses ogit.Healthcare:AnatomicalStructure ] [ ogit:belongs ogit.Healthcare:Patient ] ); . - -# ── is_a rail — the modality taxonomy (each modality is-a Assessment) ── - -ogit.Healthcare:SonographicAssessment - a rdfs:Class; - rdfs:subClassOf ogit.Healthcare:Assessment; - rdfs:label "SonographicAssessment"; - dcterms:description "An assessment performed by ultrasound (sonography). Reusable across any sonographic examination; the assessed structure is fixed by the part_of grounding (e.g. a joint, an abdomen), never by the modality."; - dcterms:source "SNOMED-CT:16310003"; - dcterms:creator "claude-ogit-bootstrap"; - ogit:scope "NTO"; - ogit:parent ogit.Healthcare:Assessment; - ogit:allowed ( - [ ogit:assesses ogit.Healthcare:AnatomicalStructure ] - ); -. - -ogit.Healthcare:MriAssessment - a rdfs:Class; - rdfs:subClassOf ogit.Healthcare:Assessment; - rdfs:label "MriAssessment"; - dcterms:description "An assessment performed by magnetic-resonance imaging. Same reusable shape as SonographicAssessment — a different modality over the identical anatomy grounding."; - dcterms:source "SNOMED-CT:113091000"; - dcterms:creator "claude-ogit-bootstrap"; - ogit:scope "NTO"; - ogit:parent ogit.Healthcare:Assessment; - ogit:allowed ( - [ ogit:assesses ogit.Healthcare:AnatomicalStructure ] - ); -. - -ogit.Healthcare:HistologicAssessment - a rdfs:Class; - rdfs:subClassOf ogit.Healthcare:Assessment; - rdfs:label "HistologicAssessment"; - dcterms:description "An assessment performed by histopathological examination of tissue. The third modality sibling; reuses the Assessment root and the anatomy grounding at zero additional shape cost."; - dcterms:source "SNOMED-CT:250537006"; - dcterms:creator "claude-ogit-bootstrap"; - ogit:scope "NTO"; - ogit:parent ogit.Healthcare:Assessment; - ogit:allowed ( - [ ogit:assesses ogit.Healthcare:AnatomicalStructure ] - ); -. - -# ── shared agnostic properties ── - -ogit.Healthcare:performedAt - a ogit:Property; - rdfs:label "performedAt"; - dcterms:description "Timestamp at which the assessment was performed."; - dcterms:source "SNOMED-CT:386053000"; - ogit:type "xsd:dateTime" . - -ogit.Healthcare:findingSummary - a ogit:Property; - rdfs:label "findingSummary"; - dcterms:description "Free-text or coded summary of the assessment's finding. Structured per-site findings are cascade-path nodes part_of the assessment, not a codebook slot."; - dcterms:source "SNOMED-CT:404684003"; - ogit:type "xsd:string" . diff --git a/NTO/Healthcare/entities/Diagnosis.ttl b/NTO/Healthcare/entities/Diagnosis.ttl index 31865491743..c442adfc795 100644 --- a/NTO/Healthcare/entities/Diagnosis.ttl +++ b/NTO/Healthcare/entities/Diagnosis.ttl @@ -9,6 +9,7 @@ ogit.Healthcare:Diagnosis a rdfs:Class; rdfs:subClassOf ogit:Entity; rdfs:label "Diagnosis"; + dcterms:valid "start=2026-10-10;"; dcterms:description "A clinical diagnosis attached to a patient (and optionally a visit). Carries ICD code, free-text diagnosis description, leading-diagnosis and chronic flags. Source-of-truth: pf_diagnosis (17 cols)."; dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_diagnosis"; dcterms:creator "claude-ogit-bootstrap"; @@ -37,59 +38,3 @@ ogit.Healthcare:Diagnosis [ ogit:relates ogit.Healthcare:Visit ] ); . - -ogit.Healthcare:fileIcd - a ogit:Property; - rdfs:label "fileIcd"; - dcterms:description "ICD-10 code for the diagnosis (pf_diagnosis.file_icd)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_diagnosis.file_icd"; - ogit:type "xsd:string" . - -ogit.Healthcare:fileDiag - a ogit:Property; - rdfs:label "fileDiag"; - dcterms:description "Short diagnosis label (pf_diagnosis.file_diag)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_diagnosis.file_diag"; - ogit:type "xsd:string" . - -ogit.Healthcare:fileText - a ogit:Property; - rdfs:label "fileText"; - dcterms:description "Free-text clinical description (pf_diagnosis.file_Text)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_diagnosis.file_Text"; - ogit:type "xsd:string" . - -ogit.Healthcare:createDate - a ogit:Property; - rdfs:label "createDate"; - dcterms:description "Entry creation timestamp (pf_diagnosis.d_createdate)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_diagnosis.d_createdate"; - ogit:type "xsd:dateTime" . - -ogit.Healthcare:dbSpezId - a ogit:Property; - rdfs:label "dbSpezId"; - dcterms:description "Specialty FK -> combo_spez.id (pf_diagnosis.db_spezid)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_diagnosis.db_spezid"; - ogit:type "xsd:integer" . - -ogit.Healthcare:fileLd - a ogit:Property; - rdfs:label "fileLd"; - dcterms:description "Leading-diagnosis flag (pf_diagnosis.file_ld)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_diagnosis.file_ld"; - ogit:type "xsd:boolean" . - -ogit.Healthcare:fileKd - a ogit:Property; - rdfs:label "fileKd"; - dcterms:description "Chronic-diagnosis flag (pf_diagnosis.file_kd)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_diagnosis.file_kd"; - ogit:type "xsd:boolean" . - -ogit.Healthcare:fileDate - a ogit:Property; - rdfs:label "fileDate"; - dcterms:description "Clinical event date the diagnosis refers to (pf_diagnosis.file_date)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_diagnosis.file_date"; - ogit:type "xsd:dateTime" . diff --git a/NTO/Healthcare/entities/HistologicAssessment.ttl b/NTO/Healthcare/entities/HistologicAssessment.ttl new file mode 100644 index 00000000000..20ed42a2d08 --- /dev/null +++ b/NTO/Healthcare/entities/HistologicAssessment.ttl @@ -0,0 +1,20 @@ +@prefix ogit: . +@prefix ogit.Healthcare: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . +@prefix xsd: . + +ogit.Healthcare:HistologicAssessment + a rdfs:Class; + rdfs:subClassOf ogit.Healthcare:Assessment; + rdfs:label "HistologicAssessment"; + dcterms:description "An assessment performed by histopathological examination of tissue. The third modality sibling; reuses the Assessment root and the anatomy grounding at zero additional shape cost."; + dcterms:source "SNOMED-CT:250537006"; + dcterms:creator "claude-ogit-bootstrap"; + ogit:scope "NTO"; + ogit:parent ogit.Healthcare:Assessment; + ogit:allowed ( + [ ogit.Healthcare:assesses ogit.Healthcare:AnatomicalStructure ] + ); +. diff --git a/NTO/Healthcare/entities/LabValue.ttl b/NTO/Healthcare/entities/LabValue.ttl index d2e2cc63bbc..5b004f6abbd 100644 --- a/NTO/Healthcare/entities/LabValue.ttl +++ b/NTO/Healthcare/entities/LabValue.ttl @@ -9,6 +9,7 @@ ogit.Healthcare:LabValue a rdfs:Class; rdfs:subClassOf ogit:Entity; rdfs:label "LabValue"; + dcterms:valid "start=2026-10-10;"; dcterms:description "A laboratory measurement for a patient. LOINC-coded test (loincid) with a numeric value (werte) and lab-internal id. Source-of-truth: pf_laboratory_values (9 cols)."; dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_laboratory_values"; dcterms:creator "claude-ogit-bootstrap"; @@ -22,6 +23,7 @@ ogit.Healthcare:LabValue ogit.Healthcare:labId ogit.Healthcare:loincId ogit.Healthcare:werte + ogit.Healthcare:loincRef ); ogit:indexed-attributes ( ogit.Healthcare:createDate @@ -29,34 +31,5 @@ ogit.Healthcare:LabValue ); ogit:allowed ( [ ogit:belongs ogit.Healthcare:Patient ] - [ ogit:relates ogit.Healthcare:loincRef ] ); . - -ogit.Healthcare:labId - a ogit:Property; - rdfs:label "labId"; - dcterms:description "Laboratory FK / partner identifier (pf_laboratory_values.labid)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_laboratory_values.labid"; - ogit:type "xsd:integer" . - -ogit.Healthcare:loincId - a ogit:Property; - rdfs:label "loincId"; - dcterms:description "LOINC reference code identifier (pf_laboratory_values.loincid)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_laboratory_values.loincid"; - ogit:type "xsd:integer" . - -ogit.Healthcare:werte - a ogit:Property; - rdfs:label "werte"; - dcterms:description "Numeric measured value (pf_laboratory_values.werte)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_laboratory_values.werte"; - ogit:type "xsd:float" . - -ogit.Healthcare:loincRef - a ogit:Property; - rdfs:label "loincRef"; - dcterms:description "Out-of-namespace reference to LOINC concept. Carried as a property reference rather than an in-namespace entity since LOINC is governed externally."; - dcterms:source "loinc:"; - ogit:type "xsd:anyURI" . diff --git a/NTO/Healthcare/entities/Medication.ttl b/NTO/Healthcare/entities/Medication.ttl index f27b605fc74..e7cf0e8e616 100644 --- a/NTO/Healthcare/entities/Medication.ttl +++ b/NTO/Healthcare/entities/Medication.ttl @@ -9,6 +9,7 @@ ogit.Healthcare:Medication a rdfs:Class; rdfs:subClassOf ogit:Entity; rdfs:label "Medication"; + dcterms:valid "start=2026-10-10;"; dcterms:description "A prescribed / dispensed medication for a patient. Carries product id, dose, interval, daily dose, dose unit, stop reason, start/stop dates. Source-of-truth: pat_medication (25 cols)."; dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pat_medication"; dcterms:creator "claude-ogit-bootstrap"; @@ -45,80 +46,3 @@ ogit.Healthcare:Medication [ ogit:relates ogit.Healthcare:combo_medication_stop_reason ] ); . - -ogit.Healthcare:filePraepId - a ogit:Property; - rdfs:label "filePraepId"; - dcterms:description "Drug-product identifier in the medicines master (pat_medication.file_praepid)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pat_medication.file_praepid"; - ogit:type "xsd:string" . - -ogit.Healthcare:fileMedicineName - a ogit:Property; - rdfs:label "fileMedicineName"; - dcterms:description "Trade / display name of the medicine (pat_medication.file_medicine_name)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pat_medication.file_medicine_name"; - ogit:type "xsd:string" . - -ogit.Healthcare:fileMedicineDose - a ogit:Property; - rdfs:label "fileMedicineDose"; - dcterms:description "Numeric per-administration dose (pat_medication.file_medicine_dose)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pat_medication.file_medicine_dose"; - ogit:type "xsd:float" . - -ogit.Healthcare:fileMedicineTyp - a ogit:Property; - rdfs:label "fileMedicineTyp"; - dcterms:description "Medicine type FK -> combo_medication_typ.id (pat_medication.file_medicine_typ)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pat_medication.file_medicine_typ"; - ogit:type "xsd:integer" . - -ogit.Healthcare:fileStart - a ogit:Property; - rdfs:label "fileStart"; - dcterms:description "Treatment start date (pat_medication.file_start)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pat_medication.file_start"; - ogit:type "xsd:dateTime" . - -ogit.Healthcare:fileStop - a ogit:Property; - rdfs:label "fileStop"; - dcterms:description "Treatment stop date (pat_medication.file_stop)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pat_medication.file_stop"; - ogit:type "xsd:dateTime" . - -ogit.Healthcare:fileNotice - a ogit:Property; - rdfs:label "fileNotice"; - dcterms:description "Free-text notice attached to the prescription (pat_medication.file_notice)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pat_medication.file_notice"; - ogit:type "xsd:string" . - -ogit.Healthcare:medicationInterval - a ogit:Property; - rdfs:label "medicationInterval"; - dcterms:description "Interval-of-administration FK -> combo_medication_interval.id (pat_medication.pf_medication_interval)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pat_medication.pf_medication_interval"; - ogit:type "xsd:integer" . - -ogit.Healthcare:medicationDailyDose - a ogit:Property; - rdfs:label "medicationDailyDose"; - dcterms:description "Daily-dose FK -> combo_medication_dailydose.id (pat_medication.pf_medication_dailydose)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pat_medication.pf_medication_dailydose"; - ogit:type "xsd:integer" . - -ogit.Healthcare:medicationStopReason - a ogit:Property; - rdfs:label "medicationStopReason"; - dcterms:description "Stop-reason FK -> combo_medication_stop_reason.id (pat_medication.pf_medication_stop_reason)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pat_medication.pf_medication_stop_reason"; - ogit:type "xsd:integer" . - -ogit.Healthcare:medicationDoseUnit - a ogit:Property; - rdfs:label "medicationDoseUnit"; - dcterms:description "Dose-unit FK -> combo_medication_dose_unit.id (pat_medication.pf_medication_dose_unit)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pat_medication.pf_medication_dose_unit"; - ogit:type "xsd:integer" . diff --git a/NTO/Healthcare/entities/MriAssessment.ttl b/NTO/Healthcare/entities/MriAssessment.ttl new file mode 100644 index 00000000000..cf3bfc719db --- /dev/null +++ b/NTO/Healthcare/entities/MriAssessment.ttl @@ -0,0 +1,20 @@ +@prefix ogit: . +@prefix ogit.Healthcare: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . +@prefix xsd: . + +ogit.Healthcare:MriAssessment + a rdfs:Class; + rdfs:subClassOf ogit.Healthcare:Assessment; + rdfs:label "MriAssessment"; + dcterms:description "An assessment performed by magnetic-resonance imaging. Same reusable shape as SonographicAssessment — a different modality over the identical anatomy grounding."; + dcterms:source "SNOMED-CT:113091000"; + dcterms:creator "claude-ogit-bootstrap"; + ogit:scope "NTO"; + ogit:parent ogit.Healthcare:Assessment; + ogit:allowed ( + [ ogit.Healthcare:assesses ogit.Healthcare:AnatomicalStructure ] + ); +. diff --git a/NTO/Healthcare/entities/Patient.ttl b/NTO/Healthcare/entities/Patient.ttl index 93d94b6ed8b..1c0d678dbe0 100644 --- a/NTO/Healthcare/entities/Patient.ttl +++ b/NTO/Healthcare/entities/Patient.ttl @@ -9,6 +9,7 @@ ogit.Healthcare:Patient a rdfs:Class; rdfs:subClassOf ogit:Entity; rdfs:label "Patient"; + dcterms:valid "start=2026-10-10;"; dcterms:description "A patient record in a clinical practice. Identified by an internal id, scoped to a tenant (clinic), with demographic, contact, and specialty fields. Source-of-truth: praxis_patient (30 cols)."; dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient"; dcterms:creator "claude-ogit-bootstrap"; @@ -35,6 +36,7 @@ ogit.Healthcare:Patient ogit.Healthcare:title1 ogit.Healthcare:title2 ogit.Healthcare:anomize + ogit:tenantId ); ogit:indexed-attributes ( ogit.Healthcare:lastname @@ -42,125 +44,5 @@ ogit.Healthcare:Patient ogit.Healthcare:mail ); ogit:allowed ( - [ ogit:belongs ogit:Tenant ] ); . - -ogit.Healthcare:firstname - a ogit:Property; - rdfs:label "firstname"; - dcterms:description "Patient given name (praxis_patient.p_firstname)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_firstname"; - ogit:type "xsd:string" . - -ogit.Healthcare:lastname - a ogit:Property; - rdfs:label "lastname"; - dcterms:description "Patient family name (praxis_patient.p_lastname)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_lastname"; - ogit:type "xsd:string" . - -ogit.Healthcare:birth - a ogit:Property; - rdfs:label "birth"; - dcterms:description "Patient date of birth (praxis_patient.p_birth)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_birth"; - ogit:type "xsd:dateTime" . - -ogit.Healthcare:sexm - a ogit:Property; - rdfs:label "sexm"; - dcterms:description "Boolean: patient identifies as male (praxis_patient.p_sexm)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_sexm"; - ogit:type "xsd:boolean" . - -ogit.Healthcare:sexf - a ogit:Property; - rdfs:label "sexf"; - dcterms:description "Boolean: patient identifies as female (praxis_patient.p_sexf)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_sexf"; - ogit:type "xsd:boolean" . - -ogit.Healthcare:sexd - a ogit:Property; - rdfs:label "sexd"; - dcterms:description "Boolean: patient identifies as diverse (praxis_patient.p_sexd)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_sexd"; - ogit:type "xsd:boolean" . - -ogit.Healthcare:ethnic - a ogit:Property; - rdfs:label "ethnic"; - dcterms:description "Self-declared ethnicity (praxis_patient.p_ethnic)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_ethnic"; - ogit:type "xsd:string" . - -ogit.Healthcare:mail - a ogit:Property; - rdfs:label "mail"; - dcterms:description "Patient email (praxis_patient.p_mail)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_mail"; - ogit:type "xsd:string" . - -ogit.Healthcare:mobil - a ogit:Property; - rdfs:label "mobil"; - dcterms:description "Patient mobile phone (praxis_patient.p_mobil)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_mobil"; - ogit:type "xsd:string" . - -ogit.Healthcare:country - a ogit:Property; - rdfs:label "country"; - dcterms:description "Patient country (praxis_patient.p_country)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_country"; - ogit:type "xsd:string" . - -ogit.Healthcare:zip - a ogit:Property; - rdfs:label "zip"; - dcterms:description "Postal code (praxis_patient.p_zip)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_zip"; - ogit:type "xsd:string" . - -ogit.Healthcare:place - a ogit:Property; - rdfs:label "place"; - dcterms:description "City / place of residence (praxis_patient.p_place)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_place"; - ogit:type "xsd:string" . - -ogit.Healthcare:street - a ogit:Property; - rdfs:label "street"; - dcterms:description "Street address (praxis_patient.p_street)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_street"; - ogit:type "xsd:string" . - -ogit.Healthcare:spez - a ogit:Property; - rdfs:label "spez"; - dcterms:description "Specialty / profile assigned to the patient (praxis_patient.p_spez)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_spez"; - ogit:type "xsd:string" . - -ogit.Healthcare:title1 - a ogit:Property; - rdfs:label "title1"; - dcterms:description "Honorific title (praxis_patient.p_title1)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_title1"; - ogit:type "xsd:string" . - -ogit.Healthcare:title2 - a ogit:Property; - rdfs:label "title2"; - dcterms:description "Academic title (praxis_patient.p_title2)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_title2"; - ogit:type "xsd:string" . - -ogit.Healthcare:anomize - a ogit:Property; - rdfs:label "anomize"; - dcterms:description "Anonymisation flag for research export (praxis_patient.p_anomize)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient.p_anomize"; - ogit:type "xsd:boolean" . diff --git a/NTO/Healthcare/entities/SonographicAssessment.ttl b/NTO/Healthcare/entities/SonographicAssessment.ttl new file mode 100644 index 00000000000..7b527fbaa56 --- /dev/null +++ b/NTO/Healthcare/entities/SonographicAssessment.ttl @@ -0,0 +1,20 @@ +@prefix ogit: . +@prefix ogit.Healthcare: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . +@prefix xsd: . + +ogit.Healthcare:SonographicAssessment + a rdfs:Class; + rdfs:subClassOf ogit.Healthcare:Assessment; + rdfs:label "SonographicAssessment"; + dcterms:description "An assessment performed by ultrasound (sonography). Reusable across any sonographic examination; the assessed structure is fixed by the part_of grounding (e.g. a joint, an abdomen), never by the modality."; + dcterms:source "SNOMED-CT:16310003"; + dcterms:creator "claude-ogit-bootstrap"; + ogit:scope "NTO"; + ogit:parent ogit.Healthcare:Assessment; + ogit:allowed ( + [ ogit.Healthcare:assesses ogit.Healthcare:AnatomicalStructure ] + ); +. diff --git a/NTO/Healthcare/entities/Treatment.ttl b/NTO/Healthcare/entities/Treatment.ttl index 2379c82542b..53db322d4a7 100644 --- a/NTO/Healthcare/entities/Treatment.ttl +++ b/NTO/Healthcare/entities/Treatment.ttl @@ -9,6 +9,7 @@ ogit.Healthcare:Treatment a rdfs:Class; rdfs:subClassOf ogit:Entity; rdfs:label "Treatment"; + dcterms:valid "start=2026-10-10;"; dcterms:description "A therapy / treatment record attached to a patient. Free-text-driven (keyword + Text), provenance-aware (db_sourcedb). Source-of-truth: pf_therapy (16 cols)."; dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_therapy"; dcterms:creator "claude-ogit-bootstrap"; @@ -34,24 +35,3 @@ ogit.Healthcare:Treatment [ ogit:belongs ogit.Healthcare:Patient ] ); . - -ogit.Healthcare:fileKeyword - a ogit:Property; - rdfs:label "fileKeyword"; - dcterms:description "Therapy keyword / short label (pf_therapy.file_keyword)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_therapy.file_keyword"; - ogit:type "xsd:string" . - -ogit.Healthcare:dbSourceDb - a ogit:Property; - rdfs:label "dbSourceDb"; - dcterms:description "Provenance: name of the source database the row was imported from (pf_therapy.db_sourcedb)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_therapy.db_sourcedb"; - ogit:type "xsd:string" . - -ogit.Healthcare:dbSourceId - a ogit:Property; - rdfs:label "dbSourceId"; - dcterms:description "Provenance: row id in the source database (pf_therapy.db_sourceid)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_therapy.db_sourceid"; - ogit:type "xsd:integer" . diff --git a/NTO/Healthcare/entities/Visit.ttl b/NTO/Healthcare/entities/Visit.ttl index acb5afaa6db..23403efdd58 100644 --- a/NTO/Healthcare/entities/Visit.ttl +++ b/NTO/Healthcare/entities/Visit.ttl @@ -9,6 +9,7 @@ ogit.Healthcare:Visit a rdfs:Class; rdfs:subClassOf ogit:Entity; rdfs:label "Visit"; + dcterms:valid "start=2026-10-10;"; dcterms:description "A patient visit / waiting-room session — login, doctor assignment, priority, time-in-waiting and logout. Source-of-truth: praxis_patient_waitingroom."; dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient_waitingroom"; dcterms:creator "claude-ogit-bootstrap"; @@ -35,52 +36,3 @@ ogit.Healthcare:Visit [ ogit:belongs ogit.Healthcare:Patient ] ); . - -ogit.Healthcare:waitingroomId - a ogit:Property; - rdfs:label "waitingroomId"; - dcterms:description "FK to praxis_waitingroom (praxis_patient_waitingroom.praxis_waitingroomid)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient_waitingroom.praxis_waitingroomid"; - ogit:type "xsd:integer" . - -ogit.Healthcare:toDocId - a ogit:Property; - rdfs:label "toDocId"; - dcterms:description "Doctor assigned to handle the visit (praxis_patient_waitingroom.p_to_doc_id)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient_waitingroom.p_to_doc_id"; - ogit:type "xsd:integer" . - -ogit.Healthcare:loginAt - a ogit:Property; - rdfs:label "loginAt"; - dcterms:description "Patient login time into waiting room (praxis_patient_waitingroom.p_login)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient_waitingroom.p_login"; - ogit:type "xsd:dateTime" . - -ogit.Healthcare:logoutAt - a ogit:Property; - rdfs:label "logoutAt"; - dcterms:description "Patient logout / visit-end time (praxis_patient_waitingroom.p_logout)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient_waitingroom.p_logout"; - ogit:type "xsd:dateTime" . - -ogit.Healthcare:timeInWaiting - a ogit:Property; - rdfs:label "timeInWaiting"; - dcterms:description "Computed waiting duration in minutes (praxis_patient_waitingroom.p_time_in_waiting)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient_waitingroom.p_time_in_waiting"; - ogit:type "xsd:integer" . - -ogit.Healthcare:priority - a ogit:Property; - rdfs:label "priority"; - dcterms:description "Visit triage priority (praxis_patient_waitingroom.p_priority)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient_waitingroom.p_priority"; - ogit:type "xsd:integer" . - -ogit.Healthcare:noticeForDoc - a ogit:Property; - rdfs:label "noticeForDoc"; - dcterms:description "Free-text reception notice for the doctor (praxis_patient_waitingroom.p_notice_for_doc)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:praxis_patient_waitingroom.p_notice_for_doc"; - ogit:type "xsd:string" . diff --git a/NTO/Healthcare/entities/VitalSign.ttl b/NTO/Healthcare/entities/VitalSign.ttl index 4e0c9445351..43919f39868 100644 --- a/NTO/Healthcare/entities/VitalSign.ttl +++ b/NTO/Healthcare/entities/VitalSign.ttl @@ -9,6 +9,7 @@ ogit.Healthcare:VitalSign a rdfs:Class; rdfs:subClassOf ogit:Entity; rdfs:label "VitalSign"; + dcterms:valid "start=2026-10-10;"; dcterms:description "A patient vital-sign measurement. Modelled here as the base class for the pf_vital_* family — the canonical / minimal source-of-truth is pf_vital_bloodpressure (id, pid, datum, sys, dia, pulse). Subclasses (pf_vital_dynmodule, pf_vital_temperature, etc.) extend with module-specific columns."; dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_vital_bloodpressure"; dcterms:creator "claude-ogit-bootstrap"; @@ -30,31 +31,3 @@ ogit.Healthcare:VitalSign [ ogit:belongs ogit.Healthcare:Patient ] ); . - -ogit.Healthcare:datum - a ogit:Property; - rdfs:label "datum"; - dcterms:description "Measurement timestamp (pf_vital_bloodpressure.datum)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_vital_bloodpressure.datum"; - ogit:type "xsd:dateTime" . - -ogit.Healthcare:bloodPressureSystolic - a ogit:Property; - rdfs:label "bloodPressureSystolic"; - dcterms:description "Systolic blood pressure in mmHg (pf_vital_bloodpressure.sys)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_vital_bloodpressure.sys"; - ogit:type "xsd:float" . - -ogit.Healthcare:bloodPressureDiastolic - a ogit:Property; - rdfs:label "bloodPressureDiastolic"; - dcterms:description "Diastolic blood pressure in mmHg (pf_vital_bloodpressure.dia)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_vital_bloodpressure.dia"; - ogit:type "xsd:float" . - -ogit.Healthcare:pulse - a ogit:Property; - rdfs:label "pulse"; - dcterms:description "Pulse rate (bpm) (pf_vital_bloodpressure.pulse)."; - dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:pf_vital_bloodpressure.pulse"; - ogit:type "xsd:float" . diff --git a/NTO/Healthcare/enumerations/combo_addtreatment.ttl b/NTO/Healthcare/entities/combo_addtreatment.ttl similarity index 93% rename from NTO/Healthcare/enumerations/combo_addtreatment.ttl rename to NTO/Healthcare/entities/combo_addtreatment.ttl index f7b7eb15e61..6c8094a8639 100644 --- a/NTO/Healthcare/enumerations/combo_addtreatment.ttl +++ b/NTO/Healthcare/entities/combo_addtreatment.ttl @@ -7,8 +7,9 @@ ogit.Healthcare:combo_addtreatment a rdfs:Class; - rdfs:subClassOf ogit:Enumeration; + rdfs:subClassOf ogit:Entity; rdfs:label "combo_addtreatment"; + dcterms:valid "start=2026-10-10;"; dcterms:description "Enumeration of additional-treatment categories attached to a clinical record. Values populated as a follow-on import from MedCare-rs."; dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:combo_addtreatment"; dcterms:creator "claude-ogit-bootstrap"; diff --git a/NTO/Healthcare/enumerations/combo_medication_dailydose.ttl b/NTO/Healthcare/entities/combo_medication_dailydose.ttl similarity index 93% rename from NTO/Healthcare/enumerations/combo_medication_dailydose.ttl rename to NTO/Healthcare/entities/combo_medication_dailydose.ttl index 27a37ae8cf2..f8007a6ed80 100644 --- a/NTO/Healthcare/enumerations/combo_medication_dailydose.ttl +++ b/NTO/Healthcare/entities/combo_medication_dailydose.ttl @@ -7,8 +7,9 @@ ogit.Healthcare:combo_medication_dailydose a rdfs:Class; - rdfs:subClassOf ogit:Enumeration; + rdfs:subClassOf ogit:Entity; rdfs:label "combo_medication_dailydose"; + dcterms:valid "start=2026-10-10;"; dcterms:description "Enumeration of medication daily-dose patterns. Values populated as a follow-on import from MedCare-rs."; dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:combo_medication_dailydose"; dcterms:creator "claude-ogit-bootstrap"; diff --git a/NTO/Healthcare/enumerations/combo_medication_dose_unit.ttl b/NTO/Healthcare/entities/combo_medication_dose_unit.ttl similarity index 93% rename from NTO/Healthcare/enumerations/combo_medication_dose_unit.ttl rename to NTO/Healthcare/entities/combo_medication_dose_unit.ttl index b2b4e57c971..fbeb11c198a 100644 --- a/NTO/Healthcare/enumerations/combo_medication_dose_unit.ttl +++ b/NTO/Healthcare/entities/combo_medication_dose_unit.ttl @@ -7,8 +7,9 @@ ogit.Healthcare:combo_medication_dose_unit a rdfs:Class; - rdfs:subClassOf ogit:Enumeration; + rdfs:subClassOf ogit:Entity; rdfs:label "combo_medication_dose_unit"; + dcterms:valid "start=2026-10-10;"; dcterms:description "Enumeration of medication dose units (mg, ml, IU, etc.). Values populated as a follow-on import from MedCare-rs."; dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:combo_medication_dose_unit"; dcterms:creator "claude-ogit-bootstrap"; diff --git a/NTO/Healthcare/enumerations/combo_medication_interval.ttl b/NTO/Healthcare/entities/combo_medication_interval.ttl similarity index 93% rename from NTO/Healthcare/enumerations/combo_medication_interval.ttl rename to NTO/Healthcare/entities/combo_medication_interval.ttl index 57e7d33ae8c..66140964810 100644 --- a/NTO/Healthcare/enumerations/combo_medication_interval.ttl +++ b/NTO/Healthcare/entities/combo_medication_interval.ttl @@ -7,8 +7,9 @@ ogit.Healthcare:combo_medication_interval a rdfs:Class; - rdfs:subClassOf ogit:Enumeration; + rdfs:subClassOf ogit:Entity; rdfs:label "combo_medication_interval"; + dcterms:valid "start=2026-10-10;"; dcterms:description "Enumeration of medication administration intervals (e.g. once-a-day, twice-a-day, weekly). Values populated as a follow-on import from MedCare-rs."; dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:combo_medication_interval"; dcterms:creator "claude-ogit-bootstrap"; diff --git a/NTO/Healthcare/enumerations/combo_medication_stop_reason.ttl b/NTO/Healthcare/entities/combo_medication_stop_reason.ttl similarity index 93% rename from NTO/Healthcare/enumerations/combo_medication_stop_reason.ttl rename to NTO/Healthcare/entities/combo_medication_stop_reason.ttl index d787d1dc6c6..f8b4357fbbc 100644 --- a/NTO/Healthcare/enumerations/combo_medication_stop_reason.ttl +++ b/NTO/Healthcare/entities/combo_medication_stop_reason.ttl @@ -7,8 +7,9 @@ ogit.Healthcare:combo_medication_stop_reason a rdfs:Class; - rdfs:subClassOf ogit:Enumeration; + rdfs:subClassOf ogit:Entity; rdfs:label "combo_medication_stop_reason"; + dcterms:valid "start=2026-10-10;"; dcterms:description "Enumeration of reasons for stopping a medication (cured, side-effect, replaced, etc.). Values populated as a follow-on import from MedCare-rs."; dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:combo_medication_stop_reason"; dcterms:creator "claude-ogit-bootstrap"; diff --git a/NTO/Healthcare/enumerations/combo_medication_typ.ttl b/NTO/Healthcare/entities/combo_medication_typ.ttl similarity index 93% rename from NTO/Healthcare/enumerations/combo_medication_typ.ttl rename to NTO/Healthcare/entities/combo_medication_typ.ttl index ccbab9fcac2..70135afdb10 100644 --- a/NTO/Healthcare/enumerations/combo_medication_typ.ttl +++ b/NTO/Healthcare/entities/combo_medication_typ.ttl @@ -7,8 +7,9 @@ ogit.Healthcare:combo_medication_typ a rdfs:Class; - rdfs:subClassOf ogit:Enumeration; + rdfs:subClassOf ogit:Entity; rdfs:label "combo_medication_typ"; + dcterms:valid "start=2026-10-10;"; dcterms:description "Enumeration of medication types (oral / IV / topical / etc.). Values populated as a follow-on import from MedCare-rs."; dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:combo_medication_typ"; dcterms:creator "claude-ogit-bootstrap"; diff --git a/NTO/Healthcare/enumerations/combo_spez.ttl b/NTO/Healthcare/entities/combo_spez.ttl similarity index 93% rename from NTO/Healthcare/enumerations/combo_spez.ttl rename to NTO/Healthcare/entities/combo_spez.ttl index 9880edf7d3a..949f351a880 100644 --- a/NTO/Healthcare/enumerations/combo_spez.ttl +++ b/NTO/Healthcare/entities/combo_spez.ttl @@ -7,8 +7,9 @@ ogit.Healthcare:combo_spez a rdfs:Class; - rdfs:subClassOf ogit:Enumeration; + rdfs:subClassOf ogit:Entity; rdfs:label "combo_spez"; + dcterms:valid "start=2026-10-10;"; dcterms:description "Enumeration of medical specialties / Fachrichtungen used to scope diagnoses, therapies and patient profiles. Values populated as a follow-on import from MedCare-rs."; dcterms:source "AdaWorldAPI/MedCare-rs/.MYSQL/Struktur.sql:combo_spez"; dcterms:creator "claude-ogit-bootstrap"; diff --git a/NTO/Healthcare/verbs/assesses.ttl b/NTO/Healthcare/verbs/assesses.ttl new file mode 100644 index 00000000000..69de74fb050 --- /dev/null +++ b/NTO/Healthcare/verbs/assesses.ttl @@ -0,0 +1,14 @@ +@prefix ogit: . +@prefix ogit.Healthcare: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.Healthcare:assesses + a owl:ObjectProperty; + rdfs:subPropertyOf ogit:Verb; + rdfs:label "assesses"; + dcterms:description "An assessment evaluates an anatomical structure and produces findings about it."; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/PTF/entities/Dummy.ttl b/NTO/PTF/entities/Dummy.ttl index 18d6e69c8b8..1bf670a30ea 100644 --- a/NTO/PTF/entities/Dummy.ttl +++ b/NTO/PTF/entities/Dummy.ttl @@ -1,5 +1,7 @@ @prefix ogit.PTF: . @prefix ogit: . +@prefix rdfs: . +@prefix dcterms: . ogit.PTF:Dummy a rdfs:Class; diff --git a/NTO/PTF/entities/HiroTopology.ttl b/NTO/PTF/entities/HiroTopology.ttl index 40c291ad8bd..b914e241f25 100644 --- a/NTO/PTF/entities/HiroTopology.ttl +++ b/NTO/PTF/entities/HiroTopology.ttl @@ -1,5 +1,7 @@ @prefix ogit.PTF: . @prefix ogit: . +@prefix rdfs: . +@prefix dcterms: . ogit.PTF:HiroTopology a rdfs:Class; diff --git a/NTO/PTF/entities/Test.ttl b/NTO/PTF/entities/Test.ttl index 84a1ab084ab..41eb47e66cd 100644 --- a/NTO/PTF/entities/Test.ttl +++ b/NTO/PTF/entities/Test.ttl @@ -1,4 +1,6 @@ @prefix ogit: . +@prefix rdfs: . +@prefix dcterms: . @prefix ogit.MARS: . @prefix ogit.PTF: . diff --git a/NTO/WorkOrder/attributes/abfahrt.ttl b/NTO/WorkOrder/attributes/abfahrt.ttl new file mode 100644 index 00000000000..82486c4087f --- /dev/null +++ b/NTO/WorkOrder/attributes/abfahrt.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:abfahrt + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "abfahrt"; + dcterms:description "Departure time as HH:MM string (max 5 chars)."; + dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.abfahrt"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/abgerechnet.ttl b/NTO/WorkOrder/attributes/abgerechnet.ttl new file mode 100644 index 00000000000..eb23bfd4db3 --- /dev/null +++ b/NTO/WorkOrder/attributes/abgerechnet.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:abgerechnet + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "abgerechnet"; + dcterms:description "Boolean billed-flag. True once the time entry has been transferred onto an invoice document, preventing double-billing."; + dcterms:source "AdaWorldAPI/WoA/models.py:TimeSheet.abgerechnet"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/adresszusatz.ttl b/NTO/WorkOrder/attributes/adresszusatz.ttl new file mode 100644 index 00000000000..20d9c3ff24c --- /dev/null +++ b/NTO/WorkOrder/attributes/adresszusatz.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:adresszusatz + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "adresszusatz"; + dcterms:description "Address line 2 (Adresszusatz). Optional second address line for c/o, building, suite, etc."; + dcterms:source "AdaWorldAPI/WoA/models.py:Customer.adresszusatz"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/aktion.ttl b/NTO/WorkOrder/attributes/aktion.ttl new file mode 100644 index 00000000000..946d0713382 --- /dev/null +++ b/NTO/WorkOrder/attributes/aktion.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:aktion + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "aktion"; + dcterms:description "Action label (Aktion) of a history entry, naming what happened to the work order. Up to 255 characters."; + dcterms:source "AdaWorldAPI/WoA/models.py:HistoryEntry.aktion"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/aktiv.ttl b/NTO/WorkOrder/attributes/aktiv.ttl new file mode 100644 index 00000000000..aabf96efc5c --- /dev/null +++ b/NTO/WorkOrder/attributes/aktiv.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:aktiv + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "aktiv"; + dcterms:description "Active flag (aktiv). Boolean; false archives or soft-disables the record instead of deleting it. Used by Article, Customer and Tenant."; + dcterms:source "AdaWorldAPI/WoA/models.py:Article.aktiv", "AdaWorldAPI/WoA/models.py:Customer.aktiv", "AdaWorldAPI/WoA/models.py:Tenant.aktiv"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/anKunde.ttl b/NTO/WorkOrder/attributes/anKunde.ttl new file mode 100644 index 00000000000..cf27382d445 --- /dev/null +++ b/NTO/WorkOrder/attributes/anKunde.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:anKunde + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "anKunde"; + dcterms:description "Customer flag (an_kunde). Boolean, default false; true includes the picture when pictures are collected for the customer."; + dcterms:source "AdaWorldAPI/WoA/models.py:Picture.an_kunde"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/anfahrten.ttl b/NTO/WorkOrder/attributes/anfahrten.ttl new file mode 100644 index 00000000000..e0a91890716 --- /dev/null +++ b/NTO/WorkOrder/attributes/anfahrten.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:anfahrten + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "anfahrten"; + dcterms:description "Number of approach trips (Anfahrten) charged to the Order. Integer count used by the trip-cost derivation against Customer.fahrtKm / fahrtKosten."; + dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.anfahrten"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/ankunft.ttl b/NTO/WorkOrder/attributes/ankunft.ttl new file mode 100644 index 00000000000..5451f45e42c --- /dev/null +++ b/NTO/WorkOrder/attributes/ankunft.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:ankunft + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "ankunft"; + dcterms:description "Arrival time at destination as HH:MM string (max 5 chars)."; + dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.ankunft"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/anrede.ttl b/NTO/WorkOrder/attributes/anrede.ttl new file mode 100644 index 00000000000..30e98873508 --- /dev/null +++ b/NTO/WorkOrder/attributes/anrede.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:anrede + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "anrede"; + dcterms:description "Form of address (Anrede). One of: Herr, Frau, Firma. Drives salutation rendering on the document."; + dcterms:source "AdaWorldAPI/WoA/models.py:Customer.anrede"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/artikelnr.ttl b/NTO/WorkOrder/attributes/artikelnr.ttl new file mode 100644 index 00000000000..e990480dd82 --- /dev/null +++ b/NTO/WorkOrder/attributes/artikelnr.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:artikelnr + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "artikelnr"; + dcterms:description "Article number (Artikelnummer). Tenant-local short code used on invoices, lookup forms and inventory listings."; + dcterms:source "AdaWorldAPI/WoA/models.py:Article.artikelnr"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/benutzername.ttl b/NTO/WorkOrder/attributes/benutzername.ttl new file mode 100644 index 00000000000..6b14f184123 --- /dev/null +++ b/NTO/WorkOrder/attributes/benutzername.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:benutzername + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "benutzername"; + dcterms:description "Plaintext account-username for the resource (max 200 chars). NOT encrypted — only the password and notes carry sensitive material."; + dcterms:source "AdaWorldAPI/WoA/models.py:PasswordEntry.benutzername"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/beschreibung.ttl b/NTO/WorkOrder/attributes/beschreibung.ttl new file mode 100644 index 00000000000..0dc674fe19a --- /dev/null +++ b/NTO/WorkOrder/attributes/beschreibung.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:beschreibung + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "beschreibung"; + dcterms:description "Free-text description (Beschreibung): the article text printed on order positions, or the work performed in a time entry."; + dcterms:source "AdaWorldAPI/WoA/models.py:Article.beschreibung", "AdaWorldAPI/WoA/models.py:TimeSheet.beschreibung"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/betreff.ttl b/NTO/WorkOrder/attributes/betreff.ttl new file mode 100644 index 00000000000..f28e1d9258a --- /dev/null +++ b/NTO/WorkOrder/attributes/betreff.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:betreff + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "betreff"; + dcterms:description "Subject / heading line (Betreff) printed at the top of the document."; + dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.betreff"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/bezahlt.ttl b/NTO/WorkOrder/attributes/bezahlt.ttl new file mode 100644 index 00000000000..f644446d1dd --- /dev/null +++ b/NTO/WorkOrder/attributes/bezahlt.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:bezahlt + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "bezahlt"; + dcterms:description "Payment-received flag. Boolean; true once the order has been settled by the customer."; + dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.bezahlt"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/bezahltAm.ttl b/NTO/WorkOrder/attributes/bezahltAm.ttl new file mode 100644 index 00000000000..d2b8f4f3c79 --- /dev/null +++ b/NTO/WorkOrder/attributes/bezahltAm.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:bezahltAm + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "bezahltAm"; + dcterms:description "Payment date (bezahlt am). ISO-8601 date set when the bezahlt flag is flipped from false to true. Drives the close-of-books reconciliation."; + dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.bezahlt_am"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/bruttoSumme.ttl b/NTO/WorkOrder/attributes/bruttoSumme.ttl new file mode 100644 index 00000000000..4093ccdcedd --- /dev/null +++ b/NTO/WorkOrder/attributes/bruttoSumme.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:bruttoSumme + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "bruttoSumme"; + dcterms:description "Gross total (Brutto-Summe) = nettoSumme + mwstBetrag."; + dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.brutto_summe"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/createdAt.ttl b/NTO/WorkOrder/attributes/createdAt.ttl new file mode 100644 index 00000000000..ae971870419 --- /dev/null +++ b/NTO/WorkOrder/attributes/createdAt.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:createdAt + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "createdAt"; + dcterms:description "ISO-8601 UTC timestamp recording when the record was first persisted."; + dcterms:source "AdaWorldAPI/WoA/models.py:Customer.created_at", "AdaWorldAPI/WoA/models.py:WorkOrder.created_at", "AdaWorldAPI/WoA/models.py"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/current.ttl b/NTO/WorkOrder/attributes/current.ttl new file mode 100644 index 00000000000..2e618d0810e --- /dev/null +++ b/NTO/WorkOrder/attributes/current.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:current + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "current"; + dcterms:description "Monotonically increasing integer holding the most-recently-issued counter value. Incremented in-place by next_val() inside a database transaction."; + dcterms:source "AdaWorldAPI/WoA/models.py:NumberSequence.current"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/dateiname.ttl b/NTO/WorkOrder/attributes/dateiname.ttl new file mode 100644 index 00000000000..28cc1d6c710 --- /dev/null +++ b/NTO/WorkOrder/attributes/dateiname.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:dateiname + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "dateiname"; + dcterms:description "Stored file name (Dateiname) of a picture on disk."; + dcterms:source "AdaWorldAPI/WoA/models.py:Picture.dateiname"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/datum.ttl b/NTO/WorkOrder/attributes/datum.ttl new file mode 100644 index 00000000000..3964450e08b --- /dev/null +++ b/NTO/WorkOrder/attributes/datum.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:datum + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "datum"; + dcterms:description "ISO-8601 calendar date of the record (Datum): the document date of an order, or the date of a logbook trip."; + dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.datum", "AdaWorldAPI/WoA/models.py:WorkOrder.datum"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/details.ttl b/NTO/WorkOrder/attributes/details.ttl new file mode 100644 index 00000000000..e1a55cb4753 --- /dev/null +++ b/NTO/WorkOrder/attributes/details.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:details + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "details"; + dcterms:description "Free-text details of a history entry."; + dcterms:source "AdaWorldAPI/WoA/models.py:HistoryEntry.details"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/docType.ttl b/NTO/WorkOrder/attributes/docType.ttl new file mode 100644 index 00000000000..8349dc88511 --- /dev/null +++ b/NTO/WorkOrder/attributes/docType.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:docType + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "docType"; + dcterms:description "Discriminator for the document variant. One of: workorder, offer, order, invoice, credit. Mirrors WorkOrder.doc_type in the source."; + dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.doc_type"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/einheit.ttl b/NTO/WorkOrder/attributes/einheit.ttl new file mode 100644 index 00000000000..46814355278 --- /dev/null +++ b/NTO/WorkOrder/attributes/einheit.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:einheit + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "einheit"; + dcterms:description "Unit (Einheit) of a line item's quantity, such as Stk. Default Stk."; + dcterms:source "AdaWorldAPI/WoA/models.py:Position.einheit"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/einzelpreis.ttl b/NTO/WorkOrder/attributes/einzelpreis.ttl new file mode 100644 index 00000000000..74bfaa127e0 --- /dev/null +++ b/NTO/WorkOrder/attributes/einzelpreis.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:einzelpreis + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "einzelpreis"; + dcterms:description "Net unit price (Einzelpreis) of a line item, a decimal with two places. Default 0."; + dcterms:source "AdaWorldAPI/WoA/models.py:Position.einzelpreis"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/ekPreis.ttl b/NTO/WorkOrder/attributes/ekPreis.ttl new file mode 100644 index 00000000000..82218aa919c --- /dev/null +++ b/NTO/WorkOrder/attributes/ekPreis.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:ekPreis + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "ekPreis"; + dcterms:description "Purchase price (Einkaufspreis) used for margin calculation against preisNetto."; + dcterms:source "AdaWorldAPI/WoA/models.py:Article.ek_preis"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/email.ttl b/NTO/WorkOrder/attributes/email.ttl new file mode 100644 index 00000000000..fb93715195c --- /dev/null +++ b/NTO/WorkOrder/attributes/email.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:email + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "email"; + dcterms:description "User's primary email address (max 200 chars). Used for notifications and password resets."; + dcterms:source "AdaWorldAPI/WoA/models.py:User.email"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/endeKm.ttl b/NTO/WorkOrder/attributes/endeKm.ttl new file mode 100644 index 00000000000..4694ad19d31 --- /dev/null +++ b/NTO/WorkOrder/attributes/endeKm.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:endeKm + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "endeKm"; + dcterms:description "Odometer reading at trip end (kilometers, float). The trip's total distance is end_km - start_km."; + dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.ende_km"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/fahrtKm.ttl b/NTO/WorkOrder/attributes/fahrtKm.ttl new file mode 100644 index 00000000000..7f4bcbb5df1 --- /dev/null +++ b/NTO/WorkOrder/attributes/fahrtKm.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:fahrtKm + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "fahrtKm"; + dcterms:description "One-way travel distance in kilometres (Fahrt km). Multiplied by Order.anfahrten and fahrtKosten to compute the trip-cost line item."; + dcterms:source "AdaWorldAPI/WoA/models.py:Customer.fahrt_km"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/fahrtKosten.ttl b/NTO/WorkOrder/attributes/fahrtKosten.ttl new file mode 100644 index 00000000000..4b9ca586154 --- /dev/null +++ b/NTO/WorkOrder/attributes/fahrtKosten.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:fahrtKosten + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "fahrtKosten"; + dcterms:description "Per-kilometre travel cost in the tenant's currency (Fahrt Kosten). Float; default 0.0. Used together with fahrtKm and Order.anfahrten to compute trip-cost line items."; + dcterms:source "AdaWorldAPI/WoA/models.py:Customer.fahrt_kosten"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/fahrzeug.ttl b/NTO/WorkOrder/attributes/fahrzeug.ttl new file mode 100644 index 00000000000..6ce7d7b9361 --- /dev/null +++ b/NTO/WorkOrder/attributes/fahrzeug.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:fahrzeug + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "fahrzeug"; + dcterms:description "Vehicle identifier (license plate or fleet name, max 100 chars)."; + dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.fahrzeug"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/firma.ttl b/NTO/WorkOrder/attributes/firma.ttl new file mode 100644 index 00000000000..678a2063331 --- /dev/null +++ b/NTO/WorkOrder/attributes/firma.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:firma + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "firma"; + dcterms:description "Company name (Firma). Empty for private customers; in that case display falls back to vorname + nachname."; + dcterms:source "AdaWorldAPI/WoA/models.py:Customer.firma"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/firstname.ttl b/NTO/WorkOrder/attributes/firstname.ttl new file mode 100644 index 00000000000..4eb618fda77 --- /dev/null +++ b/NTO/WorkOrder/attributes/firstname.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:firstname + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "firstname"; + dcterms:description "User's given name (max 120 chars)."; + dcterms:source "AdaWorldAPI/WoA/models.py:User.firstname"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/geraet.ttl b/NTO/WorkOrder/attributes/geraet.ttl new file mode 100644 index 00000000000..42e15f6da12 --- /dev/null +++ b/NTO/WorkOrder/attributes/geraet.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:geraet + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "geraet"; + dcterms:description "Device or asset (Geraet) the activity was performed on. Free text, up to 150 characters."; + dcterms:source "AdaWorldAPI/WoA/models.py:Activity.geraet"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/gruppe.ttl b/NTO/WorkOrder/attributes/gruppe.ttl new file mode 100644 index 00000000000..e1fb4a7da99 --- /dev/null +++ b/NTO/WorkOrder/attributes/gruppe.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:gruppe + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "gruppe"; + dcterms:description "Vault folder/category (max 100 chars, default 'Allgemein'). Free-form grouping used to organise entries in the UI tree."; + dcterms:source "AdaWorldAPI/WoA/models.py:PasswordEntry.gruppe"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/iban.ttl b/NTO/WorkOrder/attributes/iban.ttl new file mode 100644 index 00000000000..34a3558f603 --- /dev/null +++ b/NTO/WorkOrder/attributes/iban.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:iban + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "iban"; + dcterms:description "International Bank Account Number for SEPA payments. Optional; not modelled as a column in the current Python source but reserved here for the OGIT projection."; + dcterms:source "AdaWorldAPI/WoA/models.py:Customer"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/icon.ttl b/NTO/WorkOrder/attributes/icon.ttl new file mode 100644 index 00000000000..f5311241b39 --- /dev/null +++ b/NTO/WorkOrder/attributes/icon.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:icon + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "icon"; + dcterms:description "Icon glyph name (max 50 chars, default 'key') used by the UI to render the entry visually."; + dcterms:source "AdaWorldAPI/WoA/models.py:PasswordEntry.icon"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/intern.ttl b/NTO/WorkOrder/attributes/intern.ttl new file mode 100644 index 00000000000..6d680bec30e --- /dev/null +++ b/NTO/WorkOrder/attributes/intern.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:intern + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "intern"; + dcterms:description "Internal flag (intern). Boolean, default false; true hides the activity from the customer portal's logbook."; + dcterms:source "AdaWorldAPI/WoA/models.py:Activity.intern"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/internNotizen.ttl b/NTO/WorkOrder/attributes/internNotizen.ttl new file mode 100644 index 00000000000..6ac155b030f --- /dev/null +++ b/NTO/WorkOrder/attributes/internNotizen.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:internNotizen + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "internNotizen"; + dcterms:description "Internal notes (interne Notizen). Free-text block visible only to staff; never printed on the customer-facing document."; + dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.intern_notizen"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/isAdmin.ttl b/NTO/WorkOrder/attributes/isAdmin.ttl new file mode 100644 index 00000000000..f734b5192b1 --- /dev/null +++ b/NTO/WorkOrder/attributes/isAdmin.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:isAdmin + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "isAdmin"; + dcterms:description "Boolean flag granting admin-level access within the User's tenant scope (manage customers, articles, settings)."; + dcterms:source "AdaWorldAPI/WoA/models.py:User.is_admin"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/isSuperadmin.ttl b/NTO/WorkOrder/attributes/isSuperadmin.ttl new file mode 100644 index 00000000000..abc63d1396e --- /dev/null +++ b/NTO/WorkOrder/attributes/isSuperadmin.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:isSuperadmin + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "isSuperadmin"; + dcterms:description "Boolean flag granting cross-tenant superadmin access (manage tenants, system-wide configuration)."; + dcterms:source "AdaWorldAPI/WoA/models.py:User.is_superadmin"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/kdnr.ttl b/NTO/WorkOrder/attributes/kdnr.ttl new file mode 100644 index 00000000000..5c95de120b7 --- /dev/null +++ b/NTO/WorkOrder/attributes/kdnr.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:kdnr + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "kdnr"; + dcterms:description "Customer number (Kundennummer). Tenant-local short code used on invoices and in correspondence."; + dcterms:source "AdaWorldAPI/WoA/models.py:Customer.kdnr"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/label.ttl b/NTO/WorkOrder/attributes/label.ttl new file mode 100644 index 00000000000..298f4fe0936 --- /dev/null +++ b/NTO/WorkOrder/attributes/label.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:label + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "label"; + dcterms:description "Human-readable caption (max 200 chars) shown next to the key in admin UIs."; + dcterms:source "AdaWorldAPI/WoA/models.py:Setting.label"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/lastLogin.ttl b/NTO/WorkOrder/attributes/lastLogin.ttl new file mode 100644 index 00000000000..21e9daf5244 --- /dev/null +++ b/NTO/WorkOrder/attributes/lastLogin.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:lastLogin + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "lastLogin"; + dcterms:description "ISO-8601 UTC timestamp of the most recent successful portal login. Null on accounts that have never signed in."; + dcterms:source "AdaWorldAPI/WoA/models.py:CustomerPortalUser.last_login"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/lastname.ttl b/NTO/WorkOrder/attributes/lastname.ttl new file mode 100644 index 00000000000..6f906ffbaa9 --- /dev/null +++ b/NTO/WorkOrder/attributes/lastname.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:lastname + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "lastname"; + dcterms:description "User's family name (max 120 chars)."; + dcterms:source "AdaWorldAPI/WoA/models.py:User.lastname"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/letzteMahnung.ttl b/NTO/WorkOrder/attributes/letzteMahnung.ttl new file mode 100644 index 00000000000..b955cba49bb --- /dev/null +++ b/NTO/WorkOrder/attributes/letzteMahnung.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:letzteMahnung + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "letzteMahnung"; + dcterms:description "Date of the most recent dunning notice (letzte Mahnung). ISO-8601 date, advanced when mahnstufe is incremented."; + dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.letzte_mahnung"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/lieferant.ttl b/NTO/WorkOrder/attributes/lieferant.ttl new file mode 100644 index 00000000000..ce6a58dbc8a --- /dev/null +++ b/NTO/WorkOrder/attributes/lieferant.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:lieferant + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "lieferant"; + dcterms:description "Supplier name (Lieferant) the article is normally sourced from. Free-form string; not a relation to a separate Supplier class in the current source."; + dcterms:source "AdaWorldAPI/WoA/models.py:Article.lieferant"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/lieferantAnr.ttl b/NTO/WorkOrder/attributes/lieferantAnr.ttl new file mode 100644 index 00000000000..a0f0a87fc8d --- /dev/null +++ b/NTO/WorkOrder/attributes/lieferantAnr.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:lieferantAnr + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "lieferantAnr"; + dcterms:description "Supplier-side article number (Lieferanten-Artikelnummer). Used when reordering: the artikelnr is the tenant-local code, lieferantAnr is the SKU at the supplier."; + dcterms:source "AdaWorldAPI/WoA/models.py:Article.lieferant_anr"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/logbuch.ttl b/NTO/WorkOrder/attributes/logbuch.ttl new file mode 100644 index 00000000000..2b7c77339c8 --- /dev/null +++ b/NTO/WorkOrder/attributes/logbuch.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:logbuch + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "logbuch"; + dcterms:description "Logbook flag (Logbuch). Boolean, default false; true lists the activity or picture in the customer's logbook, both on the customer page and in the customer portal."; + dcterms:source "AdaWorldAPI/WoA/models.py:Activity.logbuch", "AdaWorldAPI/WoA/models.py:Picture.logbuch"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/logoPath.ttl b/NTO/WorkOrder/attributes/logoPath.ttl new file mode 100644 index 00000000000..af8c724b738 --- /dev/null +++ b/NTO/WorkOrder/attributes/logoPath.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:logoPath + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "logoPath"; + dcterms:description "Filesystem or URL path to the tenant's logo image used in PDF/HTML rendering of work orders."; + dcterms:source "AdaWorldAPI/WoA/models.py:Tenant.logo_path"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/mahnstufe.ttl b/NTO/WorkOrder/attributes/mahnstufe.ttl new file mode 100644 index 00000000000..fc5e3f7c560 --- /dev/null +++ b/NTO/WorkOrder/attributes/mahnstufe.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:mahnstufe + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "mahnstufe"; + dcterms:description "Dunning level (Mahnstufe). Integer 0-3: 0 = nothing sent, 1-3 = three escalation tiers per the Mahnwesen workflow."; + dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.mahnstufe"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/mailAnrede.ttl b/NTO/WorkOrder/attributes/mailAnrede.ttl new file mode 100644 index 00000000000..65443dc76b5 --- /dev/null +++ b/NTO/WorkOrder/attributes/mailAnrede.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:mailAnrede + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "mailAnrede"; + dcterms:description "Email salutation override (mail_anrede). Free-text override for the opening line of automated mails (e.g. 'Sehr geehrte Frau Mueller'). When empty, derived from anrede + nachname."; + dcterms:source "AdaWorldAPI/WoA/models.py:Customer.mail_anrede"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/menge.ttl b/NTO/WorkOrder/attributes/menge.ttl new file mode 100644 index 00000000000..e99a9fd794c --- /dev/null +++ b/NTO/WorkOrder/attributes/menge.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:menge + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "menge"; + dcterms:description "Quantity (Menge) of a line item. Default 1."; + dcterms:source "AdaWorldAPI/WoA/models.py:Position.menge"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/minuten.ttl b/NTO/WorkOrder/attributes/minuten.ttl new file mode 100644 index 00000000000..d0c6d11df15 --- /dev/null +++ b/NTO/WorkOrder/attributes/minuten.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:minuten + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "minuten"; + dcterms:description "Duration of the recorded work in minutes (integer). Display helpers round up to the nearest 15-minute increment for billing."; + dcterms:source "AdaWorldAPI/WoA/models.py:TimeSheet.minuten"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/mitarbeiter.ttl b/NTO/WorkOrder/attributes/mitarbeiter.ttl new file mode 100644 index 00000000000..a970084b6dd --- /dev/null +++ b/NTO/WorkOrder/attributes/mitarbeiter.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:mitarbeiter + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "mitarbeiter"; + dcterms:description "Crew on site (Mitarbeiter). Free-form short label or comma-separated names; the Python source keeps it as a string column rather than a User-relation join because crew composition is often informal."; + dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.mitarbeiter"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/mustChangePw.ttl b/NTO/WorkOrder/attributes/mustChangePw.ttl new file mode 100644 index 00000000000..30aa873e865 --- /dev/null +++ b/NTO/WorkOrder/attributes/mustChangePw.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:mustChangePw + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "mustChangePw"; + dcterms:description "Boolean forced-password-change marker. When true, the next successful login redirects the user to the password-reset flow."; + dcterms:source "AdaWorldAPI/WoA/models.py:User.must_change_pw"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/mwstBetrag.ttl b/NTO/WorkOrder/attributes/mwstBetrag.ttl new file mode 100644 index 00000000000..80e4982191e --- /dev/null +++ b/NTO/WorkOrder/attributes/mwstBetrag.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:mwstBetrag + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "mwstBetrag"; + dcterms:description "VAT amount (MwSt-Betrag). Derived in Python as sum(menge × einzelpreis × mwst_satz / 100) over all positions."; + dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.mwst_betrag"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/mwstSatz.ttl b/NTO/WorkOrder/attributes/mwstSatz.ttl new file mode 100644 index 00000000000..1542496e777 --- /dev/null +++ b/NTO/WorkOrder/attributes/mwstSatz.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:mwstSatz + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "mwstSatz"; + dcterms:description "VAT rate (MwSt-Satz) in percent applied when this article appears as a position. Float, default 19.0."; + dcterms:source "AdaWorldAPI/WoA/models.py:Article.mwst_satz"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/nachname.ttl b/NTO/WorkOrder/attributes/nachname.ttl new file mode 100644 index 00000000000..f3b0e3608da --- /dev/null +++ b/NTO/WorkOrder/attributes/nachname.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:nachname + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "nachname"; + dcterms:description "Family name (Nachname) of the contact person."; + dcterms:source "AdaWorldAPI/WoA/models.py:Customer.nachname"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/nettoSumme.ttl b/NTO/WorkOrder/attributes/nettoSumme.ttl new file mode 100644 index 00000000000..9317dc9d695 --- /dev/null +++ b/NTO/WorkOrder/attributes/nettoSumme.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:nettoSumme + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "nettoSumme"; + dcterms:description "Net total (Netto-Summe). Derived in Python as sum(menge × einzelpreis) over all positions; materialised on the OGIT projection."; + dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.netto_summe"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/notizen.ttl b/NTO/WorkOrder/attributes/notizen.ttl new file mode 100644 index 00000000000..8bd39fe2fa5 --- /dev/null +++ b/NTO/WorkOrder/attributes/notizen.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:notizen + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "notizen"; + dcterms:description "Free-text notes (Notizen). Internal on a customer; printed below the position list on an order."; + dcterms:source "AdaWorldAPI/WoA/models.py:Customer.notizen", "AdaWorldAPI/WoA/models.py:WorkOrder.notizen"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/notizenEnc.ttl b/NTO/WorkOrder/attributes/notizenEnc.ttl new file mode 100644 index 00000000000..c642b96accc --- /dev/null +++ b/NTO/WorkOrder/attributes/notizenEnc.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:notizenEnc + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "notizenEnc"; + dcterms:description "Fernet-encrypted ciphertext of free-form notes. Same Fernet construction as passwortEnc."; + dcterms:source "AdaWorldAPI/WoA/models.py:PasswordEntry.notizen_enc"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/orderId.ttl b/NTO/WorkOrder/attributes/orderId.ttl new file mode 100644 index 00000000000..e7c8e92b22a --- /dev/null +++ b/NTO/WorkOrder/attributes/orderId.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:orderId + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "orderId"; + dcterms:description "Public document number. The Python source carries five parallel slots (angebot_nr, auftrags_nr, workorder_nr, rechnung_nr, gutschrift_nr) plus a derived beleg_nr that picks the active one. orderId is the OGIT-side projection of beleg_nr — the human-facing identifier regardless of doc_type."; + dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.beleg_nr"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/ort.ttl b/NTO/WorkOrder/attributes/ort.ttl new file mode 100644 index 00000000000..6afde3066a7 --- /dev/null +++ b/NTO/WorkOrder/attributes/ort.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:ort + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "ort"; + dcterms:description "City / town (Ort)."; + dcterms:source "AdaWorldAPI/WoA/models.py:Customer.ort"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/passwordHash.ttl b/NTO/WorkOrder/attributes/passwordHash.ttl new file mode 100644 index 00000000000..55b7261583f --- /dev/null +++ b/NTO/WorkOrder/attributes/passwordHash.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:passwordHash + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "passwordHash"; + dcterms:description "Werkzeug-formatted password hash (pbkdf2:sha256:$$, max 256 chars). Legacy two-part SHA256+salt hashes are accepted on first login and transparently upgraded to pbkdf2."; + dcterms:source "AdaWorldAPI/WoA/models.py:User.password_hash"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/passwortEnc.ttl b/NTO/WorkOrder/attributes/passwortEnc.ttl new file mode 100644 index 00000000000..3a5b1a346af --- /dev/null +++ b/NTO/WorkOrder/attributes/passwortEnc.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:passwortEnc + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "passwortEnc"; + dcterms:description "Fernet-encrypted ciphertext of the password (base64-urlsafe over AES-128-CBC + HMAC-SHA256). Decryption requires the WOA_SECRET-derived key; failure produces the placeholder '*** Entschluesselung fehlgeschlagen ***'."; + dcterms:source "AdaWorldAPI/WoA/models.py:PasswordEntry.passwort_enc"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/pauseStunden.ttl b/NTO/WorkOrder/attributes/pauseStunden.ttl new file mode 100644 index 00000000000..c491d50797f --- /dev/null +++ b/NTO/WorkOrder/attributes/pauseStunden.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:pauseStunden + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "pauseStunden"; + dcterms:description "Break time in hours (Pause). Subtracted from (zeitEnde - zeitStart) when the labour total is computed."; + dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.pause_h"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/phone.ttl b/NTO/WorkOrder/attributes/phone.ttl new file mode 100644 index 00000000000..2c01f5559ea --- /dev/null +++ b/NTO/WorkOrder/attributes/phone.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:phone + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "phone"; + dcterms:description "User's contact telephone number (max 100 chars)."; + dcterms:source "AdaWorldAPI/WoA/models.py:User.phone"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/plz.ttl b/NTO/WorkOrder/attributes/plz.ttl new file mode 100644 index 00000000000..0432a4b734c --- /dev/null +++ b/NTO/WorkOrder/attributes/plz.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:plz + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "plz"; + dcterms:description "Postal code (Postleitzahl)."; + dcterms:source "AdaWorldAPI/WoA/models.py:Customer.plz"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/posTyp.ttl b/NTO/WorkOrder/attributes/posTyp.ttl new file mode 100644 index 00000000000..976d40e2074 --- /dev/null +++ b/NTO/WorkOrder/attributes/posTyp.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:posTyp + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "posTyp"; + dcterms:description "Line-item type (pos_typ), such as material, dienstleistung, fahrt or oneshot. Default material."; + dcterms:source "AdaWorldAPI/WoA/models.py:Position.pos_typ"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/prefix.ttl b/NTO/WorkOrder/attributes/prefix.ttl new file mode 100644 index 00000000000..777f21ca09a --- /dev/null +++ b/NTO/WorkOrder/attributes/prefix.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:prefix + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "prefix"; + dcterms:description "Static string concatenated in front of the counter when minting a new number (e.g. 'AB-', 'RE-', max 10 chars)."; + dcterms:source "AdaWorldAPI/WoA/models.py:NumberSequence.prefix"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/preisNetto.ttl b/NTO/WorkOrder/attributes/preisNetto.ttl new file mode 100644 index 00000000000..e9b0b292700 --- /dev/null +++ b/NTO/WorkOrder/attributes/preisNetto.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:preisNetto + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "preisNetto"; + dcterms:description "Net selling price (Preis netto) in the tenant's currency. Float, default 0.0."; + dcterms:source "AdaWorldAPI/WoA/models.py:Article.preis_netto"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/privatAnteil.ttl b/NTO/WorkOrder/attributes/privatAnteil.ttl new file mode 100644 index 00000000000..bfd8d660d91 --- /dev/null +++ b/NTO/WorkOrder/attributes/privatAnteil.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:privatAnteil + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "privatAnteil"; + dcterms:description "Kilometers attributed to private use within this trip (float). Subtracted from the total to derive km_geschaeftlich."; + dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.privat_anteil"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/route.ttl b/NTO/WorkOrder/attributes/route.ttl new file mode 100644 index 00000000000..4ab3d3466a1 --- /dev/null +++ b/NTO/WorkOrder/attributes/route.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:route + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "route"; + dcterms:description "Free-text route description (e.g. 'Würzburg - Frankfurt - Würzburg', max 300 chars)."; + dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.route"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/rueckfahrt.ttl b/NTO/WorkOrder/attributes/rueckfahrt.ttl new file mode 100644 index 00000000000..96be4ed761f --- /dev/null +++ b/NTO/WorkOrder/attributes/rueckfahrt.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:rueckfahrt + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "rueckfahrt"; + dcterms:description "Return-trip departure time as HH:MM string (max 5 chars)."; + dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.rueckfahrt"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/settingKey.ttl b/NTO/WorkOrder/attributes/settingKey.ttl new file mode 100644 index 00000000000..9c600aca17a --- /dev/null +++ b/NTO/WorkOrder/attributes/settingKey.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:settingKey + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "settingKey"; + dcterms:description "Setting identifier (max 100 chars), unique per tenant. Conventionally dot-namespaced (e.g. 'company.address', 'pdf.footer')."; + dcterms:source "AdaWorldAPI/WoA/models.py:Setting.key"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/settingValue.ttl b/NTO/WorkOrder/attributes/settingValue.ttl new file mode 100644 index 00000000000..c0c032cc68a --- /dev/null +++ b/NTO/WorkOrder/attributes/settingValue.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:settingValue + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "settingValue"; + dcterms:description "Free-form text value associated with the key. Application-side code is responsible for any type coercion (int/bool/json)."; + dcterms:source "AdaWorldAPI/WoA/models.py:Setting.value"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/slug.ttl b/NTO/WorkOrder/attributes/slug.ttl new file mode 100644 index 00000000000..65924e6a2cc --- /dev/null +++ b/NTO/WorkOrder/attributes/slug.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:slug + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "slug"; + dcterms:description "Unique URL-safe short identifier for the tenant (max 50 chars). Used in routing and as a stable handle independent of the numeric id."; + dcterms:source "AdaWorldAPI/WoA/models.py:Tenant.slug"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/sortOrder.ttl b/NTO/WorkOrder/attributes/sortOrder.ttl new file mode 100644 index 00000000000..5dfc3e2f2a0 --- /dev/null +++ b/NTO/WorkOrder/attributes/sortOrder.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:sortOrder + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "sortOrder"; + dcterms:description "Ordering index of a line item within its document. Integer, default 0."; + dcterms:source "AdaWorldAPI/WoA/models.py:Position.sort_order"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/startKm.ttl b/NTO/WorkOrder/attributes/startKm.ttl new file mode 100644 index 00000000000..fb29c408bf5 --- /dev/null +++ b/NTO/WorkOrder/attributes/startKm.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:startKm + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "startKm"; + dcterms:description "Odometer reading at trip start (kilometers, float)."; + dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.start_km"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/strasse.ttl b/NTO/WorkOrder/attributes/strasse.ttl new file mode 100644 index 00000000000..da4d776e116 --- /dev/null +++ b/NTO/WorkOrder/attributes/strasse.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:strasse + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "strasse"; + dcterms:description "Street and house number (Strasse)."; + dcterms:source "AdaWorldAPI/WoA/models.py:Customer.strasse"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/stundensatz.ttl b/NTO/WorkOrder/attributes/stundensatz.ttl new file mode 100644 index 00000000000..707d4be4015 --- /dev/null +++ b/NTO/WorkOrder/attributes/stundensatz.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:stundensatz + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "stundensatz"; + dcterms:description "Hourly rate (Stundensatz) in the tenant's currency, used by labour line items. Float, default 60.0."; + dcterms:source "AdaWorldAPI/WoA/models.py:Customer.stundensatz"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/taxId.ttl b/NTO/WorkOrder/attributes/taxId.ttl new file mode 100644 index 00000000000..02bd54554a5 --- /dev/null +++ b/NTO/WorkOrder/attributes/taxId.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:taxId + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "taxId"; + dcterms:description "Tax identification number (USt-IdNr / Steuernummer). Optional; reserved attribute for the OGIT projection of customer billing data."; + dcterms:source "AdaWorldAPI/WoA/models.py:Customer"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/telefon.ttl b/NTO/WorkOrder/attributes/telefon.ttl new file mode 100644 index 00000000000..fb22fe570e8 --- /dev/null +++ b/NTO/WorkOrder/attributes/telefon.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:telefon + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "telefon"; + dcterms:description "Telephone number (Telefon) of the customer. Free-form string; no E.164 normalization is enforced by the source model."; + dcterms:source "AdaWorldAPI/WoA/models.py:Customer.telefon"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/timerStart.ttl b/NTO/WorkOrder/attributes/timerStart.ttl new file mode 100644 index 00000000000..2bcf8ae4da6 --- /dev/null +++ b/NTO/WorkOrder/attributes/timerStart.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:timerStart + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "timerStart"; + dcterms:description "ISO-8601 UTC timestamp marking when a live timer was started for this entry. Null when not currently running."; + dcterms:source "AdaWorldAPI/WoA/models.py:TimeSheet.timer_start"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/titel.ttl b/NTO/WorkOrder/attributes/titel.ttl new file mode 100644 index 00000000000..752b253c917 --- /dev/null +++ b/NTO/WorkOrder/attributes/titel.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:titel + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "titel"; + dcterms:description "Display title for the entry (max 200 chars). Mandatory."; + dcterms:source "AdaWorldAPI/WoA/models.py:PasswordEntry.titel"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/unterschrift.ttl b/NTO/WorkOrder/attributes/unterschrift.ttl new file mode 100644 index 00000000000..7f9667bd23c --- /dev/null +++ b/NTO/WorkOrder/attributes/unterschrift.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:unterschrift + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "unterschrift"; + dcterms:description "Customer signature blob (Unterschrift). Base64-encoded bitmap captured at on-site checkout; not printed in PDF rendering by default."; + dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.unterschrift"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/updatedAt.ttl b/NTO/WorkOrder/attributes/updatedAt.ttl new file mode 100644 index 00000000000..3f6c75dae70 --- /dev/null +++ b/NTO/WorkOrder/attributes/updatedAt.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:updatedAt + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "updatedAt"; + dcterms:description "ISO-8601 UTC timestamp of the most recent persisted update to the record."; + dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.updated_at", "AdaWorldAPI/WoA/models.py"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/url.ttl b/NTO/WorkOrder/attributes/url.ttl new file mode 100644 index 00000000000..a12b4c0faeb --- /dev/null +++ b/NTO/WorkOrder/attributes/url.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:url + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "url"; + dcterms:description "URL of the protected resource (max 500 chars), stored in plaintext."; + dcterms:source "AdaWorldAPI/WoA/models.py:PasswordEntry.url"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/username.ttl b/NTO/WorkOrder/attributes/username.ttl new file mode 100644 index 00000000000..d10a8072546 --- /dev/null +++ b/NTO/WorkOrder/attributes/username.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:username + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "username"; + dcterms:description "Globally unique login handle (max 120 chars). Primary credential identifier used for password authentication."; + dcterms:source "AdaWorldAPI/WoA/models.py:User.username"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/versteckt.ttl b/NTO/WorkOrder/attributes/versteckt.ttl new file mode 100644 index 00000000000..9dc6dd1d76f --- /dev/null +++ b/NTO/WorkOrder/attributes/versteckt.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:versteckt + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "versteckt"; + dcterms:description "Hidden flag (versteckt). Boolean, default false; a hidden line item is left out of the DATEV export and of the customer portal's acceptance page."; + dcterms:source "AdaWorldAPI/WoA/models.py:Position.versteckt"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/vorname.ttl b/NTO/WorkOrder/attributes/vorname.ttl new file mode 100644 index 00000000000..cbf70effcf8 --- /dev/null +++ b/NTO/WorkOrder/attributes/vorname.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:vorname + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "vorname"; + dcterms:description "Given name (Vorname) of the contact person."; + dcterms:source "AdaWorldAPI/WoA/models.py:Customer.vorname"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/zahlungsziel.ttl b/NTO/WorkOrder/attributes/zahlungsziel.ttl new file mode 100644 index 00000000000..9bcbf98381f --- /dev/null +++ b/NTO/WorkOrder/attributes/zahlungsziel.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:zahlungsziel + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "zahlungsziel"; + dcterms:description "Payment term (Zahlungsziel) in days; default 14. Drives the faellig_am derivation on Order."; + dcterms:source "AdaWorldAPI/WoA/models.py:Customer.zahlungsziel"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/zeitEnde.ttl b/NTO/WorkOrder/attributes/zeitEnde.ttl new file mode 100644 index 00000000000..5c55849225f --- /dev/null +++ b/NTO/WorkOrder/attributes/zeitEnde.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:zeitEnde + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "zeitEnde"; + dcterms:description "Engagement end (Zeit Ende). End of the on-site / labour window for this Order; ISO-8601 timestamp."; + dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.zeit_ende"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/zeitStart.ttl b/NTO/WorkOrder/attributes/zeitStart.ttl new file mode 100644 index 00000000000..bcd1e8394a3 --- /dev/null +++ b/NTO/WorkOrder/attributes/zeitStart.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:zeitStart + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "zeitStart"; + dcterms:description "Engagement start (Zeit Start). Begin of the on-site / labour window for this Order; ISO-8601 timestamp."; + dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.zeit_start"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/zurueck.ttl b/NTO/WorkOrder/attributes/zurueck.ttl new file mode 100644 index 00000000000..efdae9d981f --- /dev/null +++ b/NTO/WorkOrder/attributes/zurueck.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:zurueck + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "zurueck"; + dcterms:description "Return-trip arrival time as HH:MM string (max 5 chars)."; + dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.zurueck"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/zusatzStunden.ttl b/NTO/WorkOrder/attributes/zusatzStunden.ttl new file mode 100644 index 00000000000..de1c37f6c66 --- /dev/null +++ b/NTO/WorkOrder/attributes/zusatzStunden.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:zusatzStunden + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "zusatzStunden"; + dcterms:description "Extra hours (Zusatzstunden) added on top of the (zeitEnde - zeitStart - pauseStunden) computation. Used for after-hours work or punch-list additions."; + dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.zusatz_h"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/attributes/zweck.ttl b/NTO/WorkOrder/attributes/zweck.ttl new file mode 100644 index 00000000000..5623138f03f --- /dev/null +++ b/NTO/WorkOrder/attributes/zweck.ttl @@ -0,0 +1,15 @@ +@prefix ogit.WorkOrder: . +@prefix ogit: . +@prefix owl: . +@prefix rdfs: . +@prefix dcterms: . + +ogit.WorkOrder:zweck + a owl:DatatypeProperty; + rdfs:subPropertyOf ogit:Attribute; + rdfs:label "zweck"; + dcterms:description "Business purpose of the trip (max 300 chars). Required by German tax law for the Fahrtenbuch."; + dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.zweck"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; +. diff --git a/NTO/WorkOrder/entities/Activity.ttl b/NTO/WorkOrder/entities/Activity.ttl index 075b2faa7ec..27be2d1a5fd 100644 --- a/NTO/WorkOrder/entities/Activity.ttl +++ b/NTO/WorkOrder/entities/Activity.ttl @@ -8,6 +8,7 @@ ogit.WorkOrder:Activity a rdfs:Class; rdfs:subClassOf ogit:Entity; rdfs:label "Activity"; + dcterms:valid "start=2026-10-10;"; dcterms:description "A logged work activity attached to a WorkOrder. Carries optional device/asset reference (geraet), free-text description, logbook flag (export to driver/work logbook), internal-only flag, and creation timestamp. One Activity per discrete on-site or remote action."; dcterms:source "AdaWorldAPI/WoA/models.py:Activity"; dcterms:creator "family-codec-smith"; @@ -21,10 +22,10 @@ ogit.WorkOrder:Activity ogit.WorkOrder:beschreibung ogit.WorkOrder:logbuch ogit.WorkOrder:intern - ogit:created-at + ogit:createdAt ); ogit:indexed-attributes ( - ogit:created-at + ogit:createdAt ); ogit:allowed ( [ ogit:belongs ogit.WorkOrder:Order ] diff --git a/NTO/WorkOrder/entities/Article.ttl b/NTO/WorkOrder/entities/Article.ttl index 2e7b3cdaa39..d26d364753a 100644 --- a/NTO/WorkOrder/entities/Article.ttl +++ b/NTO/WorkOrder/entities/Article.ttl @@ -7,6 +7,8 @@ ogit.WorkOrder:Article a rdfs:Class; rdfs:subClassOf ogit:Entity; rdfs:label "Article"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; dcterms:description "Article master-data record (Artikelstamm / WaWi). Referenced by Position rows on an Order via article_id. Carries selling and purchase prices, VAT rate and supplier reference; derived margin (marge / marge_pct) is computed on demand by the Python model."; dcterms:source "AdaWorldAPI/WoA/models.py:Article"; ogit:scope "NTO"; @@ -32,61 +34,3 @@ ogit.WorkOrder:Article [ ogit:belongs ogit.WorkOrder:Tenant ] ); . - -# ── attributes ─────────────────────────────────────────────────────────── - -ogit.WorkOrder:artikelnr - a rdfs:Property; - rdfs:label "artikelnr"; - dcterms:description "Article number (Artikelnummer). Tenant-local short code used on invoices, lookup forms and inventory listings."; - dcterms:source "AdaWorldAPI/WoA/models.py:Article.artikelnr"; -. - -ogit.WorkOrder:beschreibung - a rdfs:Property; - rdfs:label "beschreibung"; - dcterms:description "Article description (Beschreibung) printed on order positions. Mandatory in the source model."; - dcterms:source "AdaWorldAPI/WoA/models.py:Article.beschreibung"; -. - -ogit.WorkOrder:preisNetto - a rdfs:Property; - rdfs:label "preisNetto"; - dcterms:description "Net selling price (Preis netto) in the tenant's currency. Float, default 0.0."; - dcterms:source "AdaWorldAPI/WoA/models.py:Article.preis_netto"; -. - -ogit.WorkOrder:ekPreis - a rdfs:Property; - rdfs:label "ekPreis"; - dcterms:description "Purchase price (Einkaufspreis) used for margin calculation against preisNetto."; - dcterms:source "AdaWorldAPI/WoA/models.py:Article.ek_preis"; -. - -ogit.WorkOrder:mwstSatz - a rdfs:Property; - rdfs:label "mwstSatz"; - dcterms:description "VAT rate (MwSt-Satz) in percent applied when this article appears as a position. Float, default 19.0."; - dcterms:source "AdaWorldAPI/WoA/models.py:Article.mwst_satz"; -. - -ogit.WorkOrder:lieferant - a rdfs:Property; - rdfs:label "lieferant"; - dcterms:description "Supplier name (Lieferant) the article is normally sourced from. Free-form string; not a relation to a separate Supplier class in the current source."; - dcterms:source "AdaWorldAPI/WoA/models.py:Article.lieferant"; -. - -ogit.WorkOrder:lieferantAnr - a rdfs:Property; - rdfs:label "lieferantAnr"; - dcterms:description "Supplier-side article number (Lieferanten-Artikelnummer). Used when reordering: the artikelnr is the tenant-local code, lieferantAnr is the SKU at the supplier."; - dcterms:source "AdaWorldAPI/WoA/models.py:Article.lieferant_anr"; -. - -ogit.WorkOrder:aktiv - a rdfs:Property; - rdfs:label "aktiv"; - dcterms:description "Active flag (aktiv). Boolean; false hides the article from the new-position picker without deleting historical references. Default true."; - dcterms:source "AdaWorldAPI/WoA/models.py:Article.aktiv"; -. diff --git a/NTO/WorkOrder/entities/Customer.ttl b/NTO/WorkOrder/entities/Customer.ttl index 4785542a16e..360299183b2 100644 --- a/NTO/WorkOrder/entities/Customer.ttl +++ b/NTO/WorkOrder/entities/Customer.ttl @@ -7,6 +7,8 @@ ogit.WorkOrder:Customer a rdfs:Class; rdfs:subClassOf ogit:Entity; rdfs:label "Customer"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; dcterms:description "Customer (Kunde) of a WoA tenant. Holds postal address, billing parameters (Zahlungsziel, Stundensatz) and is the parent of all work orders, time sheets and password-vault entries belonging to that customer."; dcterms:source "AdaWorldAPI/WoA/models.py:Customer"; ogit:scope "NTO"; @@ -46,145 +48,3 @@ ogit.WorkOrder:Customer [ ogit:belongs ogit.WorkOrder:Tenant ] ); . - -# ── attributes ─────────────────────────────────────────────────────────── - -ogit.WorkOrder:kdnr - a rdfs:Property; - rdfs:label "kdnr"; - dcterms:description "Customer number (Kundennummer). Tenant-local short code used on invoices and in correspondence."; - dcterms:source "AdaWorldAPI/WoA/models.py:Customer.kdnr"; -. - -ogit.WorkOrder:firma - a rdfs:Property; - rdfs:label "firma"; - dcterms:description "Company name (Firma). Empty for private customers; in that case display falls back to vorname + nachname."; - dcterms:source "AdaWorldAPI/WoA/models.py:Customer.firma"; -. - -ogit.WorkOrder:vorname - a rdfs:Property; - rdfs:label "vorname"; - dcterms:description "Given name (Vorname) of the contact person."; - dcterms:source "AdaWorldAPI/WoA/models.py:Customer.vorname"; -. - -ogit.WorkOrder:nachname - a rdfs:Property; - rdfs:label "nachname"; - dcterms:description "Family name (Nachname) of the contact person."; - dcterms:source "AdaWorldAPI/WoA/models.py:Customer.nachname"; -. - -ogit.WorkOrder:telefon - a rdfs:Property; - rdfs:label "telefon"; - dcterms:description "Telephone number (Telefon) of the customer. Free-form string; no E.164 normalization is enforced by the source model."; - dcterms:source "AdaWorldAPI/WoA/models.py:Customer.telefon"; -. - -ogit.WorkOrder:strasse - a rdfs:Property; - rdfs:label "strasse"; - dcterms:description "Street and house number (Strasse)."; - dcterms:source "AdaWorldAPI/WoA/models.py:Customer.strasse"; -. - -ogit.WorkOrder:plz - a rdfs:Property; - rdfs:label "plz"; - dcterms:description "Postal code (Postleitzahl)."; - dcterms:source "AdaWorldAPI/WoA/models.py:Customer.plz"; -. - -ogit.WorkOrder:ort - a rdfs:Property; - rdfs:label "ort"; - dcterms:description "City / town (Ort)."; - dcterms:source "AdaWorldAPI/WoA/models.py:Customer.ort"; -. - -ogit.WorkOrder:iban - a rdfs:Property; - rdfs:label "iban"; - dcterms:description "International Bank Account Number for SEPA payments. Optional; not modelled as a column in the current Python source but reserved here for the OGIT projection."; - dcterms:source "AdaWorldAPI/WoA/models.py:Customer"; -. - -ogit.WorkOrder:taxId - a rdfs:Property; - rdfs:label "taxId"; - dcterms:description "Tax identification number (USt-IdNr / Steuernummer). Optional; reserved attribute for the OGIT projection of customer billing data."; - dcterms:source "AdaWorldAPI/WoA/models.py:Customer"; -. - -ogit.WorkOrder:zahlungsziel - a rdfs:Property; - rdfs:label "zahlungsziel"; - dcterms:description "Payment term (Zahlungsziel) in days; default 14. Drives the faellig_am derivation on Order."; - dcterms:source "AdaWorldAPI/WoA/models.py:Customer.zahlungsziel"; -. - -ogit.WorkOrder:stundensatz - a rdfs:Property; - rdfs:label "stundensatz"; - dcterms:description "Hourly rate (Stundensatz) in the tenant's currency, used by labour line items. Float, default 60.0."; - dcterms:source "AdaWorldAPI/WoA/models.py:Customer.stundensatz"; -. - -ogit.WorkOrder:anrede - a rdfs:Property; - rdfs:label "anrede"; - dcterms:description "Form of address (Anrede). One of: Herr, Frau, Firma. Drives salutation rendering on the document."; - dcterms:source "AdaWorldAPI/WoA/models.py:Customer.anrede"; -. - -ogit.WorkOrder:mailAnrede - a rdfs:Property; - rdfs:label "mailAnrede"; - dcterms:description "Email salutation override (mail_anrede). Free-text override for the opening line of automated mails (e.g. 'Sehr geehrte Frau Mueller'). When empty, derived from anrede + nachname."; - dcterms:source "AdaWorldAPI/WoA/models.py:Customer.mail_anrede"; -. - -ogit.WorkOrder:adresszusatz - a rdfs:Property; - rdfs:label "adresszusatz"; - dcterms:description "Address line 2 (Adresszusatz). Optional second address line for c/o, building, suite, etc."; - dcterms:source "AdaWorldAPI/WoA/models.py:Customer.adresszusatz"; -. - -ogit.WorkOrder:fahrtKm - a rdfs:Property; - rdfs:label "fahrtKm"; - dcterms:description "One-way travel distance in kilometres (Fahrt km). Multiplied by Order.anfahrten and fahrtKosten to compute the trip-cost line item."; - dcterms:source "AdaWorldAPI/WoA/models.py:Customer.fahrt_km"; -. - -ogit.WorkOrder:fahrtKosten - a rdfs:Property; - rdfs:label "fahrtKosten"; - dcterms:description "Per-kilometre travel cost in the tenant's currency (Fahrt Kosten). Float; default 0.0. Used together with fahrtKm and Order.anfahrten to compute trip-cost line items."; - dcterms:source "AdaWorldAPI/WoA/models.py:Customer.fahrt_kosten"; -. - -ogit.WorkOrder:notizen - a rdfs:Property; - rdfs:label "notizen"; - dcterms:description "Free-text customer notes (Notizen). Internal-only; not printed on customer-facing documents. Distinct from Order.notizen which is per-order."; - dcterms:source "AdaWorldAPI/WoA/models.py:Customer.notizen"; -. - -ogit.WorkOrder:aktiv - a rdfs:Property; - rdfs:label "aktiv"; - dcterms:description "Active flag (aktiv). Boolean; false marks the customer as archived. The Python views filter on aktiv = true for the default customer list."; - dcterms:source "AdaWorldAPI/WoA/models.py:Customer.aktiv"; -. - -ogit.WorkOrder:createdAt - a rdfs:Property; - rdfs:label "createdAt"; - dcterms:description "Customer row creation timestamp. ISO-8601, set on first insert. Mirrors the global ogit:created-at semantically; declared workspace-scoped to keep the projection consistent across WoA entities."; - dcterms:source "AdaWorldAPI/WoA/models.py:Customer.created_at"; -. diff --git a/NTO/WorkOrder/entities/CustomerPortalUser.ttl b/NTO/WorkOrder/entities/CustomerPortalUser.ttl index d86fd7b8e1d..aa72dabfd4b 100644 --- a/NTO/WorkOrder/entities/CustomerPortalUser.ttl +++ b/NTO/WorkOrder/entities/CustomerPortalUser.ttl @@ -8,6 +8,7 @@ ogit.WorkOrder:CustomerPortalUser a rdfs:Class; rdfs:subClassOf ogit:Entity; rdfs:label "CustomerPortalUser"; + dcterms:valid "start=2026-10-10;"; dcterms:description "External self-service portal login bound to exactly one Customer. Authenticates via the same werkzeug pbkdf2:sha256 scheme as the internal User (with legacy SHA256+salt fallback and transparent upgrade), but has NO admin/superadmin scope. Used by customers to view their own work orders, invoices, and pay-status. Tracks last-login for audit, and carries an active-flag for soft-disabling and a forced-password-change marker."; dcterms:source "AdaWorldAPI/WoA/models.py:CustomerPortalUser"; dcterms:creator "bus-compiler"; @@ -31,12 +32,3 @@ ogit.WorkOrder:CustomerPortalUser [ ogit:belongs ogit.WorkOrder:Customer ] ); . - -# ── attributes ─────────────────────────────────────────────────────────── - -ogit.WorkOrder:lastLogin - a rdfs:Property; - rdfs:label "lastLogin"; - dcterms:description "ISO-8601 UTC timestamp of the most recent successful portal login. Null on accounts that have never signed in."; - dcterms:source "AdaWorldAPI/WoA/models.py:CustomerPortalUser.last_login"; -. diff --git a/NTO/WorkOrder/entities/HistoryEntry.ttl b/NTO/WorkOrder/entities/HistoryEntry.ttl index e578f61f45f..8dc6b1d94b2 100644 --- a/NTO/WorkOrder/entities/HistoryEntry.ttl +++ b/NTO/WorkOrder/entities/HistoryEntry.ttl @@ -8,6 +8,7 @@ ogit.WorkOrder:HistoryEntry a rdfs:Class; rdfs:subClassOf ogit:Entity; rdfs:label "HistoryEntry"; + dcterms:valid "start=2026-10-10;"; dcterms:description "An immutable audit/log row for a WorkOrder. Records an action label (aktion), free-text details, the User who performed the action, and a creation timestamp. Append-only: every state change to an Order should produce one HistoryEntry."; dcterms:source "AdaWorldAPI/WoA/models.py:HistoryEntry"; dcterms:creator "family-codec-smith"; @@ -19,10 +20,10 @@ ogit.WorkOrder:HistoryEntry ogit:optional-attributes ( ogit.WorkOrder:aktion ogit.WorkOrder:details - ogit:created-at + ogit:createdAt ); ogit:indexed-attributes ( - ogit:created-at + ogit:createdAt ogit.WorkOrder:aktion ); ogit:allowed ( diff --git a/NTO/WorkOrder/entities/LogbookEntry.ttl b/NTO/WorkOrder/entities/LogbookEntry.ttl index 24b0cc1deaa..3a58bac1f07 100644 --- a/NTO/WorkOrder/entities/LogbookEntry.ttl +++ b/NTO/WorkOrder/entities/LogbookEntry.ttl @@ -8,6 +8,7 @@ ogit.WorkOrder:LogbookEntry a rdfs:Class; rdfs:subClassOf ogit:Entity; rdfs:label "LogbookEntry"; + dcterms:valid "start=2026-10-10;"; dcterms:description "A driving-logbook (Fahrtenbuch) row capturing one trip: start/end odometer reading, departure/arrival times, route description, business purpose, vehicle, and the private-share kilometers split out for tax purposes. Optionally references a User (driver), Customer (visited), and Order (the work-order driven for)."; dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry"; dcterms:creator "bus-compiler"; @@ -39,82 +40,3 @@ ogit.WorkOrder:LogbookEntry [ ogit:relates ogit.WorkOrder:Order ] ); . - -# ── attributes ─────────────────────────────────────────────────────────── - -ogit.WorkOrder:datum - a rdfs:Property; - rdfs:label "datum"; - dcterms:description "ISO-8601 calendar date the trip took place (mandatory; defaults to today on insert)."; - dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.datum"; -. - -ogit.WorkOrder:abfahrt - a rdfs:Property; - rdfs:label "abfahrt"; - dcterms:description "Departure time as HH:MM string (max 5 chars)."; - dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.abfahrt"; -. - -ogit.WorkOrder:ankunft - a rdfs:Property; - rdfs:label "ankunft"; - dcterms:description "Arrival time at destination as HH:MM string (max 5 chars)."; - dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.ankunft"; -. - -ogit.WorkOrder:rueckfahrt - a rdfs:Property; - rdfs:label "rueckfahrt"; - dcterms:description "Return-trip departure time as HH:MM string (max 5 chars)."; - dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.rueckfahrt"; -. - -ogit.WorkOrder:zurueck - a rdfs:Property; - rdfs:label "zurueck"; - dcterms:description "Return-trip arrival time as HH:MM string (max 5 chars)."; - dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.zurueck"; -. - -ogit.WorkOrder:startKm - a rdfs:Property; - rdfs:label "startKm"; - dcterms:description "Odometer reading at trip start (kilometers, float)."; - dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.start_km"; -. - -ogit.WorkOrder:endeKm - a rdfs:Property; - rdfs:label "endeKm"; - dcterms:description "Odometer reading at trip end (kilometers, float). The trip's total distance is end_km - start_km."; - dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.ende_km"; -. - -ogit.WorkOrder:route - a rdfs:Property; - rdfs:label "route"; - dcterms:description "Free-text route description (e.g. 'Würzburg - Frankfurt - Würzburg', max 300 chars)."; - dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.route"; -. - -ogit.WorkOrder:zweck - a rdfs:Property; - rdfs:label "zweck"; - dcterms:description "Business purpose of the trip (max 300 chars). Required by German tax law for the Fahrtenbuch."; - dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.zweck"; -. - -ogit.WorkOrder:fahrzeug - a rdfs:Property; - rdfs:label "fahrzeug"; - dcterms:description "Vehicle identifier (license plate or fleet name, max 100 chars)."; - dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.fahrzeug"; -. - -ogit.WorkOrder:privatAnteil - a rdfs:Property; - rdfs:label "privatAnteil"; - dcterms:description "Kilometers attributed to private use within this trip (float). Subtracted from the total to derive km_geschaeftlich."; - dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.privat_anteil"; -. diff --git a/NTO/WorkOrder/entities/NumberSequence.ttl b/NTO/WorkOrder/entities/NumberSequence.ttl index 56d9c45cf56..decf2eb8f47 100644 --- a/NTO/WorkOrder/entities/NumberSequence.ttl +++ b/NTO/WorkOrder/entities/NumberSequence.ttl @@ -8,6 +8,7 @@ ogit.WorkOrder:NumberSequence a rdfs:Class; rdfs:subClassOf ogit:Entity; rdfs:label "NumberSequence"; + dcterms:valid "start=2026-10-10;"; dcterms:description "Per-tenant atomic counter used to mint sequential business document numbers (offer-, order-, invoice-, credit-numbers). Each sequence has a stable name (the sequence kind), an optional prefix, and a monotonically incrementing 'current' integer; next_val() increments the counter and concatenates prefix + current to produce the next document number."; dcterms:source "AdaWorldAPI/WoA/models.py:NumberSequence"; dcterms:creator "bus-compiler"; @@ -28,19 +29,3 @@ ogit.WorkOrder:NumberSequence [ ogit:belongs ogit.WorkOrder:Tenant ] ); . - -# ── attributes ─────────────────────────────────────────────────────────── - -ogit.WorkOrder:prefix - a rdfs:Property; - rdfs:label "prefix"; - dcterms:description "Static string concatenated in front of the counter when minting a new number (e.g. 'AB-', 'RE-', max 10 chars)."; - dcterms:source "AdaWorldAPI/WoA/models.py:NumberSequence.prefix"; -. - -ogit.WorkOrder:current - a rdfs:Property; - rdfs:label "current"; - dcterms:description "Monotonically increasing integer holding the most-recently-issued counter value. Incremented in-place by next_val() inside a database transaction."; - dcterms:source "AdaWorldAPI/WoA/models.py:NumberSequence.current"; -. diff --git a/NTO/WorkOrder/entities/Order.ttl b/NTO/WorkOrder/entities/Order.ttl index 0422834d581..2ae51686302 100644 --- a/NTO/WorkOrder/entities/Order.ttl +++ b/NTO/WorkOrder/entities/Order.ttl @@ -7,6 +7,8 @@ ogit.WorkOrder:Order a rdfs:Class; rdfs:subClassOf ogit:Entity; rdfs:label "Order"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; dcterms:description "Central work-order document of WoA. A single SQLAlchemy table 'workorders' represents five doc_type variants — workorder (Arbeitsauftrag), offer (Angebot), order (Auftrag), invoice (Rechnung), credit (Gutschrift). The doc_type column selects the variant; OGIT consumers should treat 'Order' as the umbrella class and filter by doc_type for variant-specific logic. Holds positions, activities, pictures and history as child collections, and exposes derived totals netto_summe / mwst_betrag / brutto_summe."; dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder"; ogit:scope "NTO"; @@ -51,159 +53,3 @@ ogit.WorkOrder:Order [ ogit:relates ogit.WorkOrder:Article ] ); . - -# ── attributes ─────────────────────────────────────────────────────────── - -ogit.WorkOrder:orderId - a rdfs:Property; - rdfs:label "orderId"; - dcterms:description "Public document number. The Python source carries five parallel slots (angebot_nr, auftrags_nr, workorder_nr, rechnung_nr, gutschrift_nr) plus a derived beleg_nr that picks the active one. orderId is the OGIT-side projection of beleg_nr — the human-facing identifier regardless of doc_type."; - dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.beleg_nr"; -. - -ogit.WorkOrder:docType - a rdfs:Property; - rdfs:label "docType"; - dcterms:description "Discriminator for the document variant. One of: workorder, offer, order, invoice, credit. Mirrors WorkOrder.doc_type in the source."; - dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.doc_type"; -. - -ogit.WorkOrder:datum - a rdfs:Property; - rdfs:label "datum"; - dcterms:description "Document date (Datum). ISO-8601 date; defaults to creation day. Drives the dunning / due-date derivations."; - dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.datum"; -. - -ogit.WorkOrder:betreff - a rdfs:Property; - rdfs:label "betreff"; - dcterms:description "Subject / heading line (Betreff) printed at the top of the document."; - dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.betreff"; -. - -ogit.WorkOrder:nettoSumme - a rdfs:Property; - rdfs:label "nettoSumme"; - dcterms:description "Net total (Netto-Summe). Derived in Python as sum(menge × einzelpreis) over all positions; materialised on the OGIT projection."; - dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.netto_summe"; -. - -ogit.WorkOrder:mwstBetrag - a rdfs:Property; - rdfs:label "mwstBetrag"; - dcterms:description "VAT amount (MwSt-Betrag). Derived in Python as sum(menge × einzelpreis × mwst_satz / 100) over all positions."; - dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.mwst_betrag"; -. - -ogit.WorkOrder:bruttoSumme - a rdfs:Property; - rdfs:label "bruttoSumme"; - dcterms:description "Gross total (Brutto-Summe) = nettoSumme + mwstBetrag."; - dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.brutto_summe"; -. - -ogit.WorkOrder:bezahlt - a rdfs:Property; - rdfs:label "bezahlt"; - dcterms:description "Payment-received flag. Boolean; true once the order has been settled by the customer."; - dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.bezahlt"; -. - -ogit.WorkOrder:zeitStart - a rdfs:Property; - rdfs:label "zeitStart"; - dcterms:description "Engagement start (Zeit Start). Begin of the on-site / labour window for this Order; ISO-8601 timestamp."; - dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.zeit_start"; -. - -ogit.WorkOrder:zeitEnde - a rdfs:Property; - rdfs:label "zeitEnde"; - dcterms:description "Engagement end (Zeit Ende). End of the on-site / labour window for this Order; ISO-8601 timestamp."; - dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.zeit_ende"; -. - -ogit.WorkOrder:anfahrten - a rdfs:Property; - rdfs:label "anfahrten"; - dcterms:description "Number of approach trips (Anfahrten) charged to the Order. Integer count used by the trip-cost derivation against Customer.fahrtKm / fahrtKosten."; - dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.anfahrten"; -. - -ogit.WorkOrder:mitarbeiter - a rdfs:Property; - rdfs:label "mitarbeiter"; - dcterms:description "Crew on site (Mitarbeiter). Free-form short label or comma-separated names; the Python source keeps it as a string column rather than a User-relation join because crew composition is often informal."; - dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.mitarbeiter"; -. - -ogit.WorkOrder:pauseStunden - a rdfs:Property; - rdfs:label "pauseStunden"; - dcterms:description "Break time in hours (Pause). Subtracted from (zeitEnde - zeitStart) when the labour total is computed."; - dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.pause_h"; -. - -ogit.WorkOrder:zusatzStunden - a rdfs:Property; - rdfs:label "zusatzStunden"; - dcterms:description "Extra hours (Zusatzstunden) added on top of the (zeitEnde - zeitStart - pauseStunden) computation. Used for after-hours work or punch-list additions."; - dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.zusatz_h"; -. - -ogit.WorkOrder:notizen - a rdfs:Property; - rdfs:label "notizen"; - dcterms:description "Customer-facing notes (Notizen). Free-text block printed on the document below the position list."; - dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.notizen"; -. - -ogit.WorkOrder:internNotizen - a rdfs:Property; - rdfs:label "internNotizen"; - dcterms:description "Internal notes (interne Notizen). Free-text block visible only to staff; never printed on the customer-facing document."; - dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.intern_notizen"; -. - -ogit.WorkOrder:bezahltAm - a rdfs:Property; - rdfs:label "bezahltAm"; - dcterms:description "Payment date (bezahlt am). ISO-8601 date set when the bezahlt flag is flipped from false to true. Drives the close-of-books reconciliation."; - dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.bezahlt_am"; -. - -ogit.WorkOrder:mahnstufe - a rdfs:Property; - rdfs:label "mahnstufe"; - dcterms:description "Dunning level (Mahnstufe). Integer 0-3: 0 = nothing sent, 1-3 = three escalation tiers per the Mahnwesen workflow."; - dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.mahnstufe"; -. - -ogit.WorkOrder:letzteMahnung - a rdfs:Property; - rdfs:label "letzteMahnung"; - dcterms:description "Date of the most recent dunning notice (letzte Mahnung). ISO-8601 date, advanced when mahnstufe is incremented."; - dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.letzte_mahnung"; -. - -ogit.WorkOrder:unterschrift - a rdfs:Property; - rdfs:label "unterschrift"; - dcterms:description "Customer signature blob (Unterschrift). Base64-encoded bitmap captured at on-site checkout; not printed in PDF rendering by default."; - dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.unterschrift"; -. - -ogit.WorkOrder:createdAt - a rdfs:Property; - rdfs:label "createdAt"; - dcterms:description "Order row creation timestamp. ISO-8601, set on first insert. Distinct from datum (the document's effective date)."; - dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.created_at"; -. - -ogit.WorkOrder:updatedAt - a rdfs:Property; - rdfs:label "updatedAt"; - dcterms:description "Order row last-modified timestamp. ISO-8601, advanced on every UPDATE. Distinct from datum and bezahltAm."; - dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.updated_at"; -. diff --git a/NTO/WorkOrder/entities/PasswordEntry.ttl b/NTO/WorkOrder/entities/PasswordEntry.ttl index 695f8d6179a..12e5167da77 100644 --- a/NTO/WorkOrder/entities/PasswordEntry.ttl +++ b/NTO/WorkOrder/entities/PasswordEntry.ttl @@ -8,6 +8,7 @@ ogit.WorkOrder:PasswordEntry a rdfs:Class; rdfs:subClassOf ogit:Entity; rdfs:label "PasswordEntry"; + dcterms:valid "start=2026-10-10;"; dcterms:description "KeePass-style encrypted vault entry stored against a Customer (the resource being accessed) and authored by a User. Sensitive fields (passwort_enc, notizen_enc) are encrypted at rest using Fernet (AES-128-CBC + HMAC-SHA256), with the symmetric key derived from the WOA_SECRET environment variable via SHA-256. WOA_SECRET MUST be set (>= 32 chars); the application refuses to encrypt or decrypt without it. Non-secret metadata (group, title, username, url, icon) is stored in plaintext for searchability."; dcterms:source "AdaWorldAPI/WoA/models.py:PasswordEntry"; dcterms:creator "bus-compiler"; @@ -38,54 +39,3 @@ ogit.WorkOrder:PasswordEntry [ ogit:relates ogit.WorkOrder:User ] ); . - -# ── attributes ─────────────────────────────────────────────────────────── - -ogit.WorkOrder:gruppe - a rdfs:Property; - rdfs:label "gruppe"; - dcterms:description "Vault folder/category (max 100 chars, default 'Allgemein'). Free-form grouping used to organise entries in the UI tree."; - dcterms:source "AdaWorldAPI/WoA/models.py:PasswordEntry.gruppe"; -. - -ogit.WorkOrder:titel - a rdfs:Property; - rdfs:label "titel"; - dcterms:description "Display title for the entry (max 200 chars). Mandatory."; - dcterms:source "AdaWorldAPI/WoA/models.py:PasswordEntry.titel"; -. - -ogit.WorkOrder:benutzername - a rdfs:Property; - rdfs:label "benutzername"; - dcterms:description "Plaintext account-username for the resource (max 200 chars). NOT encrypted — only the password and notes carry sensitive material."; - dcterms:source "AdaWorldAPI/WoA/models.py:PasswordEntry.benutzername"; -. - -ogit.WorkOrder:passwortEnc - a rdfs:Property; - rdfs:label "passwortEnc"; - dcterms:description "Fernet-encrypted ciphertext of the password (base64-urlsafe over AES-128-CBC + HMAC-SHA256). Decryption requires the WOA_SECRET-derived key; failure produces the placeholder '*** Entschluesselung fehlgeschlagen ***'."; - dcterms:source "AdaWorldAPI/WoA/models.py:PasswordEntry.passwort_enc"; -. - -ogit.WorkOrder:url - a rdfs:Property; - rdfs:label "url"; - dcterms:description "URL of the protected resource (max 500 chars), stored in plaintext."; - dcterms:source "AdaWorldAPI/WoA/models.py:PasswordEntry.url"; -. - -ogit.WorkOrder:notizenEnc - a rdfs:Property; - rdfs:label "notizenEnc"; - dcterms:description "Fernet-encrypted ciphertext of free-form notes. Same Fernet construction as passwortEnc."; - dcterms:source "AdaWorldAPI/WoA/models.py:PasswordEntry.notizen_enc"; -. - -ogit.WorkOrder:icon - a rdfs:Property; - rdfs:label "icon"; - dcterms:description "Icon glyph name (max 50 chars, default 'key') used by the UI to render the entry visually."; - dcterms:source "AdaWorldAPI/WoA/models.py:PasswordEntry.icon"; -. diff --git a/NTO/WorkOrder/entities/Picture.ttl b/NTO/WorkOrder/entities/Picture.ttl index c54d3781de5..d240affcfdd 100644 --- a/NTO/WorkOrder/entities/Picture.ttl +++ b/NTO/WorkOrder/entities/Picture.ttl @@ -8,6 +8,7 @@ ogit.WorkOrder:Picture a rdfs:Class; rdfs:subClassOf ogit:Entity; rdfs:label "Picture"; + dcterms:valid "start=2026-10-10;"; dcterms:description "An image/document attachment on a WorkOrder. Carries the stored filename (dateiname), an optional caption (beschreibung), a logbook flag (include in driver/work logbook export), an an_kunde flag (visible to / send to customer), and a creation timestamp."; dcterms:source "AdaWorldAPI/WoA/models.py:Picture"; dcterms:creator "family-codec-smith"; @@ -21,10 +22,10 @@ ogit.WorkOrder:Picture ogit.WorkOrder:beschreibung ogit.WorkOrder:logbuch ogit.WorkOrder:anKunde - ogit:created-at + ogit:createdAt ); ogit:indexed-attributes ( - ogit:created-at + ogit:createdAt ); ogit:allowed ( [ ogit:belongs ogit.WorkOrder:Order ] diff --git a/NTO/WorkOrder/entities/Position.ttl b/NTO/WorkOrder/entities/Position.ttl index 042d12711d8..a0e66c7fddf 100644 --- a/NTO/WorkOrder/entities/Position.ttl +++ b/NTO/WorkOrder/entities/Position.ttl @@ -8,6 +8,7 @@ ogit.WorkOrder:Position a rdfs:Class; rdfs:subClassOf ogit:Entity; rdfs:label "Position"; + dcterms:valid "start=2026-10-10;"; dcterms:description "A line item (Position) on a WorkOrder/Order document. Represents one billable or descriptive row carrying quantity, unit, unit-price, VAT rate, optional Article reference, and ordering. Used across offer/order/invoice/credit doc types."; dcterms:source "AdaWorldAPI/WoA/models.py:Position"; dcterms:creator "family-codec-smith"; diff --git a/NTO/WorkOrder/entities/Setting.ttl b/NTO/WorkOrder/entities/Setting.ttl index b62d6fc4248..07fccdac775 100644 --- a/NTO/WorkOrder/entities/Setting.ttl +++ b/NTO/WorkOrder/entities/Setting.ttl @@ -8,6 +8,7 @@ ogit.WorkOrder:Setting a rdfs:Class; rdfs:subClassOf ogit:Entity; rdfs:label "Setting"; + dcterms:valid "start=2026-10-10;"; dcterms:description "Per-tenant key/value configuration row. Stores arbitrary string-typed application settings (e.g. company address fields, default VAT rate, PDF footer text) outside of code. The label provides a human-readable caption used in admin UIs; the value is free-form text."; dcterms:source "AdaWorldAPI/WoA/models.py:Setting"; dcterms:creator "bus-compiler"; @@ -28,26 +29,3 @@ ogit.WorkOrder:Setting [ ogit:belongs ogit.WorkOrder:Tenant ] ); . - -# ── attributes ─────────────────────────────────────────────────────────── - -ogit.WorkOrder:settingKey - a rdfs:Property; - rdfs:label "settingKey"; - dcterms:description "Setting identifier (max 100 chars), unique per tenant. Conventionally dot-namespaced (e.g. 'company.address', 'pdf.footer')."; - dcterms:source "AdaWorldAPI/WoA/models.py:Setting.key"; -. - -ogit.WorkOrder:settingValue - a rdfs:Property; - rdfs:label "settingValue"; - dcterms:description "Free-form text value associated with the key. Application-side code is responsible for any type coercion (int/bool/json)."; - dcterms:source "AdaWorldAPI/WoA/models.py:Setting.value"; -. - -ogit.WorkOrder:label - a rdfs:Property; - rdfs:label "label"; - dcterms:description "Human-readable caption (max 200 chars) shown next to the key in admin UIs."; - dcterms:source "AdaWorldAPI/WoA/models.py:Setting.label"; -. diff --git a/NTO/WorkOrder/entities/Tenant.ttl b/NTO/WorkOrder/entities/Tenant.ttl index cc20f07ba5d..6b5e3324136 100644 --- a/NTO/WorkOrder/entities/Tenant.ttl +++ b/NTO/WorkOrder/entities/Tenant.ttl @@ -7,6 +7,8 @@ ogit.WorkOrder:Tenant a rdfs:Class; rdfs:subClassOf ogit:Entity; rdfs:label "Tenant"; + dcterms:valid "start=2026-10-10;"; + dcterms:creator "claude-ogit-bootstrap"; dcterms:description "Multi-tenancy root for the WoA application. Every tenant-scoped business entity (Customer, Order, Article, etc.) carries a tenant_id foreign key referring to this class. Backed by SQLAlchemy table 'tenants'."; dcterms:source "AdaWorldAPI/WoA/models.py:Tenant"; ogit:scope "NTO"; @@ -25,34 +27,6 @@ ogit.WorkOrder:Tenant ogit:id ogit.WorkOrder:slug ); -. - -# ── attributes ─────────────────────────────────────────────────────────── - -ogit.WorkOrder:slug - a rdfs:Property; - rdfs:label "slug"; - dcterms:description "Unique URL-safe short identifier for the tenant (max 50 chars). Used in routing and as a stable handle independent of the numeric id."; - dcterms:source "AdaWorldAPI/WoA/models.py:Tenant.slug"; -. - -ogit.WorkOrder:aktiv - a rdfs:Property; - rdfs:label "aktiv"; - dcterms:description "Boolean active-flag. Inactive tenants are soft-disabled rather than deleted."; - dcterms:source "AdaWorldAPI/WoA/models.py:Tenant.aktiv"; -. - -ogit.WorkOrder:logoPath - a rdfs:Property; - rdfs:label "logoPath"; - dcterms:description "Filesystem or URL path to the tenant's logo image used in PDF/HTML rendering of work orders."; - dcterms:source "AdaWorldAPI/WoA/models.py:Tenant.logo_path"; -. - -ogit.WorkOrder:createdAt - a rdfs:Property; - rdfs:label "createdAt"; - dcterms:description "ISO-8601 UTC timestamp recording when the entity was first persisted."; - dcterms:source "AdaWorldAPI/WoA/models.py"; + ogit:allowed ( + ); . diff --git a/NTO/WorkOrder/entities/TimeSheet.ttl b/NTO/WorkOrder/entities/TimeSheet.ttl index d51ac6ef854..26261b4ce67 100644 --- a/NTO/WorkOrder/entities/TimeSheet.ttl +++ b/NTO/WorkOrder/entities/TimeSheet.ttl @@ -8,6 +8,7 @@ ogit.WorkOrder:TimeSheet a rdfs:Class; rdfs:subClassOf ogit:Entity; rdfs:label "TimeSheet"; + dcterms:valid "start=2026-10-10;"; dcterms:description "Stundenzettel — a time-tracking record booked against a Customer (mandatory) and authored by a User (optional). Stores duration in minutes, a free-text description, and an 'abgerechnet' flag indicating whether the entry has been billed onto a downstream invoice. Provides 15-minute-rounded display values and decimal-hour conversion via instance methods."; dcterms:source "AdaWorldAPI/WoA/models.py:TimeSheet"; dcterms:creator "bus-compiler"; @@ -35,40 +36,3 @@ ogit.WorkOrder:TimeSheet [ ogit:relates ogit.WorkOrder:User ] ); . - -# ── attributes ─────────────────────────────────────────────────────────── - -ogit.WorkOrder:minuten - a rdfs:Property; - rdfs:label "minuten"; - dcterms:description "Duration of the recorded work in minutes (integer). Display helpers round up to the nearest 15-minute increment for billing."; - dcterms:source "AdaWorldAPI/WoA/models.py:TimeSheet.minuten"; -. - -ogit.WorkOrder:timerStart - a rdfs:Property; - rdfs:label "timerStart"; - dcterms:description "ISO-8601 UTC timestamp marking when a live timer was started for this entry. Null when not currently running."; - dcterms:source "AdaWorldAPI/WoA/models.py:TimeSheet.timer_start"; -. - -ogit.WorkOrder:abgerechnet - a rdfs:Property; - rdfs:label "abgerechnet"; - dcterms:description "Boolean billed-flag. True once the time entry has been transferred onto an invoice document, preventing double-billing."; - dcterms:source "AdaWorldAPI/WoA/models.py:TimeSheet.abgerechnet"; -. - -ogit.WorkOrder:beschreibung - a rdfs:Property; - rdfs:label "beschreibung"; - dcterms:description "Free-text description of the work performed during this time entry."; - dcterms:source "AdaWorldAPI/WoA/models.py:TimeSheet.beschreibung"; -. - -ogit.WorkOrder:updatedAt - a rdfs:Property; - rdfs:label "updatedAt"; - dcterms:description "ISO-8601 UTC timestamp of the most recent persisted update to the row."; - dcterms:source "AdaWorldAPI/WoA/models.py"; -. diff --git a/NTO/WorkOrder/entities/User.ttl b/NTO/WorkOrder/entities/User.ttl index aed69a1908a..a5d8738378d 100644 --- a/NTO/WorkOrder/entities/User.ttl +++ b/NTO/WorkOrder/entities/User.ttl @@ -8,6 +8,7 @@ ogit.WorkOrder:User a rdfs:Class; rdfs:subClassOf ogit:Entity; rdfs:label "User"; + dcterms:valid "start=2026-10-10;"; dcterms:description "Internal WoA user / operator. Authenticates against pbkdf2:sha256 password hashes (werkzeug, 600k iterations, salted) with transparent migration from a legacy SHA256+salt format. Carries optional contact metadata (firstname, lastname, email, phone), the admin/superadmin flags that gate Flask routes, and a forced-password-change marker. Each User belongs to exactly one Tenant via tenant_id."; dcterms:source "AdaWorldAPI/WoA/models.py:User"; dcterms:creator "bus-compiler"; @@ -35,68 +36,3 @@ ogit.WorkOrder:User [ ogit:belongs ogit.WorkOrder:Tenant ] ); . - -# ── attributes ─────────────────────────────────────────────────────────── - -ogit.WorkOrder:username - a rdfs:Property; - rdfs:label "username"; - dcterms:description "Globally unique login handle (max 120 chars). Primary credential identifier used for password authentication."; - dcterms:source "AdaWorldAPI/WoA/models.py:User.username"; -. - -ogit.WorkOrder:passwordHash - a rdfs:Property; - rdfs:label "passwordHash"; - dcterms:description "Werkzeug-formatted password hash (pbkdf2:sha256:$$, max 256 chars). Legacy two-part SHA256+salt hashes are accepted on first login and transparently upgraded to pbkdf2."; - dcterms:source "AdaWorldAPI/WoA/models.py:User.password_hash"; -. - -ogit.WorkOrder:firstname - a rdfs:Property; - rdfs:label "firstname"; - dcterms:description "User's given name (max 120 chars)."; - dcterms:source "AdaWorldAPI/WoA/models.py:User.firstname"; -. - -ogit.WorkOrder:lastname - a rdfs:Property; - rdfs:label "lastname"; - dcterms:description "User's family name (max 120 chars)."; - dcterms:source "AdaWorldAPI/WoA/models.py:User.lastname"; -. - -ogit.WorkOrder:email - a rdfs:Property; - rdfs:label "email"; - dcterms:description "User's primary email address (max 200 chars). Used for notifications and password resets."; - dcterms:source "AdaWorldAPI/WoA/models.py:User.email"; -. - -ogit.WorkOrder:phone - a rdfs:Property; - rdfs:label "phone"; - dcterms:description "User's contact telephone number (max 100 chars)."; - dcterms:source "AdaWorldAPI/WoA/models.py:User.phone"; -. - -ogit.WorkOrder:isAdmin - a rdfs:Property; - rdfs:label "isAdmin"; - dcterms:description "Boolean flag granting admin-level access within the User's tenant scope (manage customers, articles, settings)."; - dcterms:source "AdaWorldAPI/WoA/models.py:User.is_admin"; -. - -ogit.WorkOrder:isSuperadmin - a rdfs:Property; - rdfs:label "isSuperadmin"; - dcterms:description "Boolean flag granting cross-tenant superadmin access (manage tenants, system-wide configuration)."; - dcterms:source "AdaWorldAPI/WoA/models.py:User.is_superadmin"; -. - -ogit.WorkOrder:mustChangePw - a rdfs:Property; - rdfs:label "mustChangePw"; - dcterms:description "Boolean forced-password-change marker. When true, the next successful login redirects the user to the password-reset flow."; - dcterms:source "AdaWorldAPI/WoA/models.py:User.must_change_pw"; -. diff --git a/NTO/WorkOrder/verbs/AccessesPortal.ttl b/NTO/WorkOrder/verbs/accessesPortal.ttl similarity index 100% rename from NTO/WorkOrder/verbs/AccessesPortal.ttl rename to NTO/WorkOrder/verbs/accessesPortal.ttl diff --git a/NTO/WorkOrder/verbs/Assigned.ttl b/NTO/WorkOrder/verbs/assigned.ttl similarity index 100% rename from NTO/WorkOrder/verbs/Assigned.ttl rename to NTO/WorkOrder/verbs/assigned.ttl diff --git a/NTO/WorkOrder/verbs/BelongsToTenant.ttl b/NTO/WorkOrder/verbs/belongsToTenant.ttl similarity index 100% rename from NTO/WorkOrder/verbs/BelongsToTenant.ttl rename to NTO/WorkOrder/verbs/belongsToTenant.ttl diff --git a/NTO/WorkOrder/verbs/Drives.ttl b/NTO/WorkOrder/verbs/drives.ttl similarity index 100% rename from NTO/WorkOrder/verbs/Drives.ttl rename to NTO/WorkOrder/verbs/drives.ttl diff --git a/NTO/WorkOrder/verbs/HasActivity.ttl b/NTO/WorkOrder/verbs/hasActivity.ttl similarity index 100% rename from NTO/WorkOrder/verbs/HasActivity.ttl rename to NTO/WorkOrder/verbs/hasActivity.ttl diff --git a/NTO/WorkOrder/verbs/HasHistory.ttl b/NTO/WorkOrder/verbs/hasHistory.ttl similarity index 100% rename from NTO/WorkOrder/verbs/HasHistory.ttl rename to NTO/WorkOrder/verbs/hasHistory.ttl diff --git a/NTO/WorkOrder/verbs/HasPicture.ttl b/NTO/WorkOrder/verbs/hasPicture.ttl similarity index 100% rename from NTO/WorkOrder/verbs/HasPicture.ttl rename to NTO/WorkOrder/verbs/hasPicture.ttl diff --git a/NTO/WorkOrder/verbs/HasPosition.ttl b/NTO/WorkOrder/verbs/hasPosition.ttl similarity index 100% rename from NTO/WorkOrder/verbs/HasPosition.ttl rename to NTO/WorkOrder/verbs/hasPosition.ttl diff --git a/NTO/WorkOrder/verbs/Issued.ttl b/NTO/WorkOrder/verbs/issued.ttl similarity index 100% rename from NTO/WorkOrder/verbs/Issued.ttl rename to NTO/WorkOrder/verbs/issued.ttl diff --git a/NTO/WorkOrder/verbs/LogsTime.ttl b/NTO/WorkOrder/verbs/logsTime.ttl similarity index 100% rename from NTO/WorkOrder/verbs/LogsTime.ttl rename to NTO/WorkOrder/verbs/logsTime.ttl diff --git a/NTO/WorkOrder/verbs/OwnsPasswords.ttl b/NTO/WorkOrder/verbs/ownsPasswords.ttl similarity index 100% rename from NTO/WorkOrder/verbs/OwnsPasswords.ttl rename to NTO/WorkOrder/verbs/ownsPasswords.ttl diff --git a/NTO/WorkOrder/verbs/RefersToArticle.ttl b/NTO/WorkOrder/verbs/refersToArticle.ttl similarity index 100% rename from NTO/WorkOrder/verbs/RefersToArticle.ttl rename to NTO/WorkOrder/verbs/refersToArticle.ttl diff --git a/src/lib.rs b/src/lib.rs index 5df0ee2cb19..d28d8770ea5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,7 +64,7 @@ include!(concat!(env!("OUT_DIR"), "/ttls.rs")); /// /// Note: this is a linear scan (`ALL_TTLS.len()` ~ 1500). For hot paths /// the consumer's own build.rs should generate a perfect-hash table. -pub fn by_branch(branch: &str) -> impl Iterator { +pub fn by_branch(branch: &str) -> impl Iterator + '_ { ALL_TTLS.iter().filter(move |t| t.branch == branch) } diff --git a/tools/README.md b/tools/README.md new file mode 100644 index 00000000000..e434e20c55a --- /dev/null +++ b/tools/README.md @@ -0,0 +1,85 @@ +# tools + +## `ogit_validate.py` — the OGIT validator without Java + +`validate.sh` runs `bin/ogit-validator.jar`. `ogit_validate.py` does the same +checks in Python 3.8+ with no packages, and prints the jar's messages on the +same streams with the same exit status. With no arguments it validates the +files `validate.sh` finds from the current directory. + +```sh +python3 -I tools/ogit_validate.py +python3 -I tools/ogit_validate.py --no-color --all +python3 -I tools/test_ogit_validate.py +``` + +The jar runs three stages, each only if the one before passed: + +1. Per file (only with more than one file): `ogit*` prefixes must follow the + `http://www.purl.org//` convention; the file name + must be the local name of the file's first subject; and the canonical + path must contain `//…/.ttl`. +2. The ten queries in the jar (`validation0.rq` … `validation9.rq`): required + properties of entities, attributes and verbs, `ogit:history` items, + date formats, validation type and parameter. +3. The entity registry: referenced attributes, parents, allowed targets and + verbs must exist; scopes and cardinalities must be known; labels, + descriptions, creators and `dcterms:valid` start dates must be present. + +Two things decide the output and are easy to miss: + +- **The directory check reads the canonical path.** The root-namespace files + under `SGO/sgo/` and `SGO/core/` pass because the checkout directory is + named `OGIT`. In a checkout with another name they fail with + `Directory mismatch`, with the jar and with this port alike. +- **Statements are sorted.** rdf4j's memory store returns statements in + lexical order, not file order. That decides a file's first subject and the + order of the stage 3 messages. + +Stage 2 reports one subject per failing query. Which one the jar reports +depends on rdf4j's grouping; the port reports the first in store order, so +the subject can differ while the failing queries are the same. `--all` lists +every failing subject. + +`test_ogit_validate.py` runs 19 cases and compares the port with output +recorded from the jar; with Java installed it also runs the jar. On this +repository the port and the jar agree in a checkout named `OGIT`, in a +checkout under another name, and on `master`. + +## `import_check.py` — strict consistency check for importers + +`validate.sh` runs the OGIT validator, which checks what this repository +requires of its own files. `import_check.py` checks what a program that +imports OGIT into another schema needs on top of that, and can write the verb +catalogue and the allowed-edge table that such an import reads. + +It is not wired into `validate.sh` or CI and changes no file. It needs only +Python 3.8+ and no packages. + +```sh +python3 -I tools/import_check.py +python3 -I tools/import_check.py --emit-verbs verbs.tsv --emit-allowed allowed.tsv +python3 -I tools/import_check.py --exclude NTO/PTF --warn C03 +python3 -I tools/test_import_check.py +``` + +| code | finding | +|---|---| +| C01 | a prefixed name uses a prefix the file does not declare | +| C02 | a verb has neither supported shape: `owl:ObjectProperty` + `rdfs:subPropertyOf ogit:Verb`, or `rdfs:Class` + `rdfs:subClassOf ogit:Verb` with `ogit:from-to` | +| C03 | the file name differs from the local name of the term it declares | +| C04 | an `ogit:allowed` or `ogit:from-to` verb is not declared | +| C05 | an allowed or from-to source/target is not a declared entity | +| C06 | an allowed cell is not one `[ verb target ]` pair | +| C07 | a term is declared in more than one file | +| C08 | a class does not reach `ogit:Entity`, `ogit:Verb` or another OGIT base class | + +Exit status is 0 with no errors, 1 with errors, 2 when a file cannot be +parsed. `--warn CODE` reports a check without failing; `--exclude PATH` +skips a directory. + +`--emit-verbs` writes `verb, file, shape, uses`, where shape is +`object-property`, `property`, `class` or `undeclared`. `--emit-allowed` +writes `source, verb, target, file, origin`, where origin is `allowed` +(an entity's `ogit:allowed` list) or `from-to` (a verb's `ogit:from-to` +list). diff --git a/tools/__pycache__/import_check.cpython-313.pyc b/tools/__pycache__/import_check.cpython-313.pyc new file mode 100644 index 00000000000..623c4f71e3e Binary files /dev/null and b/tools/__pycache__/import_check.cpython-313.pyc differ diff --git a/tools/__pycache__/ogit_validate.cpython-313.pyc b/tools/__pycache__/ogit_validate.cpython-313.pyc new file mode 100644 index 00000000000..349d84583e4 Binary files /dev/null and b/tools/__pycache__/ogit_validate.cpython-313.pyc differ diff --git a/tools/import_check.py b/tools/import_check.py new file mode 100644 index 00000000000..109fe5a5508 --- /dev/null +++ b/tools/import_check.py @@ -0,0 +1,520 @@ +#!/usr/bin/env python3 +"""Strict OGIT consistency check for importers. + +`validate.sh` runs the OGIT validator, which checks what the OGIT project +requires of its own files. A program that imports OGIT into another schema +needs more: every verb an entity allows must be declared, every allowed +target must be an entity, and every verb must have the one declaration shape +an importer can read. This script checks those import-time rules and can +write the verb catalogue and the allowed (source, verb, target) table. + +It is not part of `validate.sh` or CI, and it changes no file. Run it before +an import: + + python3 -I tools/import_check.py + python3 -I tools/import_check.py --emit-verbs verbs.tsv --emit-allowed allowed.tsv + +Exit status: 0 no errors, 1 errors found, 2 a file could not be parsed or +the arguments were wrong. `--warn CODE` reports a check without failing. + +Checks: + C01 a prefixed name uses a prefix the file does not declare + C02 a verb has neither supported shape: `owl:ObjectProperty` + + `rdfs:subPropertyOf ogit:Verb`, or `rdfs:Class` + `rdfs:subClassOf + ogit:Verb` with an `ogit:from-to` list (the verb-as-class form) + C03 the file name differs from the local name of the term it declares + C04 an `ogit:allowed` or `ogit:from-to` verb is not declared anywhere + C05 an `ogit:allowed` or `ogit:from-to` target is not a declared entity + C06 an `ogit:allowed` cell is not `[ verb target ]` with one pair + C07 a term is declared in more than one file + C08 a class is not a subclass, directly or through declared classes, + of `ogit:Entity`, `ogit:Verb` or another OGIT base class + +Only the Turtle used in this repository is parsed: `@prefix`, prefixed +names, ``, string literals (including triple-quoted ones and language +tags), lists, blank nodes, `a`, and the `;` `,` `.` separators. +""" + +import argparse +import os +import re +import sys +from collections import defaultdict + +OGIT = "http://www.purl.org/ogit/" +RDF_TYPE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" +RDFS = "http://www.w3.org/2000/01/rdf-schema#" +OWL = "http://www.w3.org/2002/07/owl#" + +RDFS_CLASS = RDFS + "Class" +SUBCLASS = RDFS + "subClassOf" +SUBPROP = RDFS + "subPropertyOf" +OBJECT_PROPERTY = OWL + "ObjectProperty" +ENTITY = OGIT + "Entity" +VERB = OGIT + "Verb" +ALLOWED = OGIT + "allowed" +FROM_TO = OGIT + "from-to" +FROM = OGIT + "from" +TO = OGIT + "to" + +ALL_CODES = ["C01", "C02", "C03", "C04", "C05", "C06", "C07", "C08"] + +SEPARATORS = set("()[];,") + + +class ParseError(Exception): + pass + + +# -------------------------------------------------------------------------- +# Tokenizer +# -------------------------------------------------------------------------- + +def tokenize(text): + """Yield (kind, value, line) for the Turtle subset described above.""" + i, n, line = 0, len(text), 1 + while i < n: + c = text[i] + if c == "\n": + line += 1 + i += 1 + elif c in " \t\r": + i += 1 + elif c == "#": + j = text.find("\n", i) + i = n if j < 0 else j + elif c == "<": + j = text.find(">", i) + if j < 0: + raise ParseError(f"line {line}: unterminated IRI") + yield ("iri", text[i + 1:j], line) + i = j + 1 + elif c in "\"'": + q3 = text[i:i + 3] + if q3 in ('"""', "'''"): + j = i + 3 + while j < n and text[j:j + 3] != q3: + j += 2 if text[j] == "\\" else 1 + if j >= n: + raise ParseError(f"line {line}: unterminated long string") + value = text[i + 3:j] + start_line = line + line += value.count("\n") + i = j + 3 + else: + j = i + 1 + while j < n and text[j] != c: + if text[j] == "\\": + j += 1 + if j < n and text[j] == "\n": + raise ParseError(f"line {line}: newline in string") + j += 1 + if j >= n: + raise ParseError(f"line {line}: unterminated string") + value = text[i + 1:j] + start_line = line + i = j + 1 + lang = None + if i < n and text[i] == "@": + m = re.compile(r"@[A-Za-z]+(-[A-Za-z0-9]+)*").match(text, i) + if m: + lang = m.group(0)[1:] + i = m.end() + yield ("lit", (value, lang), start_line) + elif c in SEPARATORS: + yield (c, c, line) + i += 1 + elif c == ".": + yield (".", ".", line) + i += 1 + else: + m = re.compile(r"[^\s()\[\];,\"'<#]+").match(text, i) + word = m.group(0) + i = m.end() + # A local name cannot end with '.', so a trailing dot closes + # the statement ("ogit:Node." is "ogit:Node" then "."). + tail = 0 + while word.endswith(".") and len(word) > 1: + word = word[:-1] + tail += 1 + yield ("word", word, line) + for _ in range(tail): + yield (".", ".", line) + + +# -------------------------------------------------------------------------- +# Parser +# -------------------------------------------------------------------------- + +class Term: + __slots__ = ("kind", "value", "line", "curie", "lang", "datatype") + + def __init__(self, kind, value, line, curie=None, lang=None): + self.kind = kind # iri | lit | list | bnode + self.value = value # str | list[Term] | list[(Term, Term)] + self.line = line + self.curie = curie # prefixed spelling, when written that way + self.lang = lang # language tag of a literal + self.datatype = None # datatype IRI of a literal (set by ogit_validate) + + +class FileParse: + def __init__(self, path): + self.path = path + self.prefixes = {} + self.statements = [] # (subject Term, [(pred Term, obj Term)]) + self.undeclared = [] # (line, curie) + + +def parse(path, text, default_prefixes=None): + """Parse one file. `default_prefixes` resolves prefixes a file uses + without declaring them, as rdf4j's parser does; import_check passes none.""" + fp = FileParse(path) + defaults = default_prefixes or {} + toks = list(tokenize(text)) + pos = 0 + + def peek(): + return toks[pos] if pos < len(toks) else (None, None, None) + + def take(kind=None): + nonlocal pos + if pos >= len(toks): + raise ParseError("unexpected end of file") + tok = toks[pos] + if kind is not None and tok[0] != kind: + raise ParseError(f"line {tok[2]}: expected {kind!r}, got {tok[1]!r}") + pos += 1 + return tok + + def resolve(word, line): + if word == "a": + return Term("iri", RDF_TYPE, line, "a") + if word in ("true", "false") or re.fullmatch(r"[+-]?\d+(\.\d+)?", word): + t = Term("lit", word, line) + kind = "boolean" if word in ("true", "false") else "decimal" if "." in word else "integer" + t.datatype = "http://www.w3.org/2001/XMLSchema#" + kind + return t + if ":" not in word: + raise ParseError(f"line {line}: bare word {word!r}") + prefix, local = word.split(":", 1) + if prefix not in fp.prefixes and prefix in defaults: + return Term("iri", defaults[prefix] + local, line, word) + if prefix not in fp.prefixes: + fp.undeclared.append((line, word)) + return Term("iri", "urn:undeclared:" + word, line, word) + return Term("iri", fp.prefixes[prefix] + local, line, word) + + def obj(): + kind, value, line = peek() + if kind == "iri": + take() + return Term("iri", value, line) + if kind == "lit": + take() + return Term("lit", value[0], line, lang=value[1]) + if kind == "word": + take() + return resolve(value, line) + if kind == "(": + take() + items = [] + while peek()[0] != ")": + items.append(obj()) + take(")") + return Term("list", items, line) + if kind == "[": + take() + pairs = pred_obj_list(close="]") + take("]") + return Term("bnode", pairs, line) + raise ParseError(f"line {line}: unexpected {value!r}") + + def pred_obj_list(close): + pairs = [] + while True: + kind = peek()[0] + if kind in (close, None): + return pairs + if kind == ";": + take() + continue + pkind, pvalue, pline = take() + if pkind == "iri": + pred = Term("iri", pvalue, pline) + elif pkind == "word": + pred = resolve(pvalue, pline) + else: + raise ParseError(f"line {pline}: expected a predicate, got {pvalue!r}") + pairs.append((pred, obj())) + while peek()[0] == ",": + take() + pairs.append((pred, obj())) + if peek()[0] not in (";", close): + raise ParseError(f"line {peek()[2]}: expected ';' or {close!r}, got {peek()[1]!r}") + + while pos < len(toks): + kind, value, line = peek() + if kind == "word" and value in ("@prefix", "@PREFIX"): + take() + _, name, pline = take("word") + if not name.endswith(":"): + raise ParseError(f"line {pline}: bad prefix name {name!r}") + _, iri, _ = take("iri") + fp.prefixes[name[:-1]] = iri + take(".") + continue + subject = obj() + pairs = pred_obj_list(close=".") + take(".") + fp.statements.append((subject, pairs)) + return fp + + +# -------------------------------------------------------------------------- +# Model +# -------------------------------------------------------------------------- + +def curie_of(iri, prefixes): + """Spell an IRI with the repository's prefix convention.""" + if iri.startswith("urn:undeclared:"): + return iri[len("urn:undeclared:"):] + best = None + for name, base in prefixes.items(): + if iri.startswith(base) and (best is None or len(base) > len(prefixes[best])): + best = name + if best is None: + return "<" + iri + ">" + return best + ":" + iri[len(prefixes[best]):] + + +def local_name(iri): + return re.split(r"[/#]", iri)[-1] + + +def build(parses): + decls = defaultdict(list) # iri -> [(path, {pred: [obj]})] + prefixes = {} + for fp in parses: + for name, base in fp.prefixes.items(): + prefixes.setdefault(name, base) + for subject, pairs in fp.statements: + if subject.kind != "iri": + continue + props = defaultdict(list) + for pred, o in pairs: + props[pred.value].append(o) + decls[subject.value].append((fp.path, props, subject.line)) + return decls, prefixes + + +def iris(objs): + return {o.value for o in objs if o.kind == "iri"} + + +def classify(decls): + types = {iri: set().union(*(iris(p.get(RDF_TYPE, [])) for _, p, _ in ds)) + for iri, ds in decls.items()} + supers = {iri: set().union(*(iris(p.get(SUBCLASS, [])) for _, p, _ in ds)) + for iri, ds in decls.items()} + subprops = {iri: set().union(*(iris(p.get(SUBPROP, [])) for _, p, _ in ds)) + for iri, ds in decls.items()} + + classes = {iri for iri, t in types.items() if RDFS_CLASS in t} + + def reaches(iri, root, seen=None): + seen = seen or set() + for s in supers.get(iri, ()): + if s == root: + return True + if s not in seen and s in classes: + seen.add(s) + if reaches(s, root, seen): + return True + return False + + entities = {iri for iri in classes if iri != ENTITY and reaches(iri, ENTITY)} + property_verbs = {iri for iri, sp in subprops.items() if VERB in sp} + class_verbs = {iri for iri in classes if reaches(iri, VERB)} + return types, supers, classes, entities, property_verbs, class_verbs, reaches + + +# -------------------------------------------------------------------------- +# Checks +# -------------------------------------------------------------------------- + +def run(root, files): + findings = [] # (code, path, line, message) + parses = [] + for path in files: + rel = os.path.relpath(path, root) + try: + with open(path, encoding="utf-8") as fh: + text = fh.read() + fp = parse(rel, text) + except (ParseError, UnicodeDecodeError) as exc: + return None, [("PARSE", rel, 0, str(exc))] + for line, curie in fp.undeclared: + findings.append(("C01", rel, line, f"prefix of {curie} is not declared in this file")) + parses.append(fp) + + decls, prefixes = build(parses) + types, supers, classes, entities, pverbs, cverbs, reaches = classify(decls) + verbs = pverbs | cverbs + base_classes = {iri for iri in classes if iri.startswith(OGIT) and "/" not in iri[len(OGIT):]} + + def c(iri): + return curie_of(iri, prefixes) + + # C07, C03, C02, C08 + for iri, ds in decls.items(): + if iri.startswith("urn:undeclared:"): + continue + if len(ds) > 1: + where = ", ".join(p for p, _, _ in ds) + findings.append(("C07", ds[0][0], ds[0][2], f"{c(iri)} is declared in {len(ds)} files: {where}")) + for path, props, line in ds: + stem = os.path.splitext(os.path.basename(path))[0] + if os.path.basename(path) != "ogit.ttl" and local_name(iri) != stem: + findings.append(("C03", path, line, f"{c(iri)} is declared in {os.path.basename(path)}")) + if iri in cverbs and not any(p.get(FROM_TO) for _, p, _ in ds): + findings.append(("C02", ds[0][0], ds[0][2], + f"{c(iri)} is an rdfs:Class under ogit:Verb without an ogit:from-to list")) + elif iri in pverbs and OBJECT_PROPERTY not in types.get(iri, ()): + findings.append(("C02", ds[0][0], ds[0][2], + f"{c(iri)} is a sub-property of ogit:Verb but not an owl:ObjectProperty")) + if iri in classes and iri not in base_classes and iri not in entities and iri not in cverbs: + if not any(reaches(iri, b) for b in base_classes): + missing = sorted(c(s) for s in supers.get(iri, ()) if s not in classes) + why = f" (super class {', '.join(missing)} is not declared)" if missing else "" + findings.append(("C08", ds[0][0], ds[0][2], + f"class {c(iri)} does not reach ogit:Entity or another OGIT base class{why}")) + + # Edges: ogit:allowed on entities, ogit:from-to on verbs. + edges = [] # (src, verb, dst, path, line, origin) + for iri, ds in decls.items(): + for path, props, line in ds: + for lst in props.get(ALLOWED, []): + cells = lst.value if lst.kind == "list" else [lst] + for cell in cells: + if cell.kind != "bnode" or len(cell.value) != 1: + findings.append(("C06", path, cell.line, + f"allowed cell of {c(iri)} is not one [ verb target ] pair")) + continue + verb, dst = cell.value[0] + if dst.kind != "iri": + findings.append(("C06", path, cell.line, + f"allowed target of {c(iri)} via {c(verb.value)} is not a term")) + continue + edges.append((iri, verb.value, dst.value, path, cell.line, "allowed")) + for lst in props.get(FROM_TO, []): + cells = lst.value if lst.kind == "list" else [lst] + for cell in cells: + froms = [o for p, o in cell.value if p.value == FROM] if cell.kind == "bnode" else [] + tos = [o for p, o in cell.value if p.value == TO] if cell.kind == "bnode" else [] + if len(froms) != 1 or len(tos) != 1 or froms[0].kind != "iri" or tos[0].kind != "iri": + findings.append(("C06", path, cell.line, + f"from-to cell of {c(iri)} is not one [ ogit:from X ; ogit:to Y ]")) + continue + edges.append((froms[0].value, iri, tos[0].value, path, cell.line, "from-to")) + + for src, verb, dst, path, line, origin in edges: + if verb not in verbs: + findings.append(("C04", path, line, f"verb {c(verb)} ({origin} of {c(src)}) is not declared")) + for end, label in ((src, "source"), (dst, "target")): + if origin == "allowed" and label == "source": + continue + if end not in entities: + findings.append(("C05", path, line, + f"{label} {c(end)} of {c(verb)} ({origin}) is not a declared entity")) + + model = { + "decls": decls, "prefixes": prefixes, "verbs": verbs, "pverbs": pverbs, + "cverbs": cverbs, "types": types, "edges": edges, "entities": entities, + } + return model, findings + + +def write_verbs(path, model): + c = lambda iri: curie_of(iri, model["prefixes"]) + uses = defaultdict(int) + for _, verb, _, _, _, _ in model["edges"]: + uses[verb] += 1 + with open(path, "w", encoding="utf-8") as fh: + fh.write("verb\tfile\tshape\tuses\n") + for verb in sorted(set(model["verbs"]) | set(uses), key=c): + ds = model["decls"].get(verb) + file = ds[0][0] if ds else "" + if verb in model["cverbs"]: + shape = "class" + elif verb in model["pverbs"] and OBJECT_PROPERTY in model["types"].get(verb, ()): + shape = "object-property" + elif verb in model["pverbs"]: + shape = "property" + else: + shape = "undeclared" + fh.write(f"{c(verb)}\t{file}\t{shape}\t{uses[verb]}\n") + + +def write_allowed(path, model): + c = lambda iri: curie_of(iri, model["prefixes"]) + rows = sorted({(c(s), c(v), c(d), p, o) for s, v, d, p, _, o in model["edges"]}) + with open(path, "w", encoding="utf-8") as fh: + fh.write("source\tverb\ttarget\tfile\torigin\n") + for row in rows: + fh.write("\t".join(row) + "\n") + + +def main(argv=None): + ap = argparse.ArgumentParser(description=__doc__.split("\n\n")[0]) + ap.add_argument("root", nargs="?", default=".", help="repository root (default: .)") + ap.add_argument("--warn", action="append", default=[], metavar="CODE", + choices=ALL_CODES, help="report CODE without failing (repeatable)") + ap.add_argument("--emit-verbs", metavar="TSV", help="write the verb catalogue") + ap.add_argument("--emit-allowed", metavar="TSV", help="write the (source, verb, target) table") + ap.add_argument("--exclude", action="append", default=[], metavar="PATH", + help="skip files under PATH, relative to root (repeatable)") + ap.add_argument("--quiet", action="store_true", help="print only the summary") + args = ap.parse_args(argv) + + root = os.path.abspath(args.root) + files = [] + for dirpath, dirnames, filenames in os.walk(root): + dirnames[:] = sorted(d for d in dirnames if d != ".git") + rel_dir = os.path.relpath(dirpath, root) + if any(rel_dir == e or rel_dir.startswith(e.rstrip("/") + os.sep) + for e in (os.path.normpath(x) for x in args.exclude)): + continue + for name in sorted(filenames): + if name.endswith(".ttl") and not name.startswith("graphit-ontology."): + files.append(os.path.join(dirpath, name)) + + model, findings = run(root, files) + if model is None: + for code, path, line, msg in findings: + print(f"{path}: {code}: {msg}", file=sys.stderr) + return 2 + + findings.sort(key=lambda f: (f[0], f[1], f[2])) + counts = defaultdict(int) + for code, path, line, msg in findings: + counts[code] += 1 + if not args.quiet: + level = "warning" if code in args.warn else "error" + print(f"{path}:{line}: {level} {code}: {msg}") + + if args.emit_verbs: + write_verbs(args.emit_verbs, model) + if args.emit_allowed: + write_allowed(args.emit_allowed, model) + + errors = sum(n for code, n in counts.items() if code not in args.warn) + summary = ", ".join(f"{code}={counts[code]}" for code in ALL_CODES if counts[code]) + print(f"import_check: {len(files)} files, {len(model['entities'])} entities, " + f"{len(model['verbs'])} verbs, {len(model['edges'])} edges; " + f"{errors} error(s){'; ' + summary if summary else ''}") + return 1 if errors else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/ogit_validate.py b/tools/ogit_validate.py new file mode 100644 index 00000000000..59dac754629 --- /dev/null +++ b/tools/ogit_validate.py @@ -0,0 +1,825 @@ +#!/usr/bin/env python3 +"""Python port of bin/ogit-validator.jar. + +`validate.sh` runs the OGIT validator, a Scala/Java jar from 2019. This +script does the same checks in Python, so the ontology can be validated +where no Java is installed. It takes the same arguments and prints the +same messages, on the same streams, with the same exit status: + + python3 -I tools/ogit_validate.py $(find . -type f ! -name 'graphit-ontology.*' -name '*.ttl') + +or, as `validate.sh` does, through xargs. With no arguments it finds the +files the way `validate.sh` does, from the current directory. + +The behaviour was read from the jar's bytecode (`co.arago.meerkat.validator` +and `de.arago.ogit.tools`) and checked against the jar on the repository and +on fixtures (`tools/test_ogit_validate.py`). The jar runs three stages; a +stage runs only if the one before it passed. + +1. Per file, only when more than one file is given: + - every `@prefix ogit...:` line must declare + `http://www.purl.org/` + the prefix with `.` replaced by `/` + `/`; + the first one that doesn't stops the run; + - files whose path contains `ogit.ttl` are not parsed; every other file + is parsed on its own, and a Turtle error stops the run with a Java + stack trace; + - the file name must be the local name of the file's first subject plus + `.ttl` (reported, not fatal); + - the file's canonical path must match `//(.*)/`, + case-insensitive; the first one that doesn't stops the run. The + namespace dir is the subject IRI's path between `www.purl.org/` and the + local name, with `ogit/` removed. +2. Ten SPARQL queries (`validation0.rq` ... `validation9.rq` in the jar) run + over all files together. Each failing query prints one line. +3. The entity registry: every referenced attribute, parent, allowed target + and allowed verb must exist, scopes and cardinalities must be known, and + entities, verbs and attributes need a label, a description, a creator and + a `dcterms:valid` start date. + +Order. rdf4j's in-memory store commits each add through a sorted TreeModel, +so statements come back sorted (LexicalValueComparator: blank nodes, IRIs, +literals; IRIs by string), not in file order. This port sorts the same way. +That decides the first subject of a file in stage 1, and the order of the +stage 3 messages and the value read when a property has several. + +The directory check uses the canonical path. Root-namespace files such as +SGO/sgo/entities/Node.ttl pass only because the checkout itself is named +OGIT (or ogit): the pattern /ogit/(.*)/node.ttl then matches. In a checkout +under another name the same files fail. + +Known differences from the jar: + - Stage 2 names one subject per failing query: the first group the query + engine produces. That order depends on rdf4j's grouping and on the order + of the arguments, and no model of it reproduced the jar. This port + reports the first failing subject in store order; the set of failing + queries is the same. `--all` lists every subject. + - The prefix error names the jar's data-source object, which Java prints + as `co.arago.meerkat.validator.FileDataSource@`. The hash is not + reproducible; this port prints a hash of the path. + - Turtle syntax errors are reported with this parser's message and line, + not rdf4j's (except for an undeclared prefix, whose message matches). + - Only the Turtle this repository uses is parsed (see import_check.py), + plus `^^` datatypes and string escapes. + +Exit status: 0 valid, 1 invalid. +""" + +import argparse +import os +import re +import sys +import time + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import import_check as ic # noqa: E402 + +OGIT = "http://www.purl.org/ogit/" +RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#" +RDFS = "http://www.w3.org/2000/01/rdf-schema#" +DCT = "http://purl.org/dc/terms/" +XSD = "http://www.w3.org/2001/XMLSchema#" + +RDF_FIRST, RDF_REST, RDF_NIL = RDF + "first", RDF + "rest", RDF + "nil" +SUBCLASS, SUBPROP, LABEL = RDFS + "subClassOf", RDFS + "subPropertyOf", RDFS + "label" + +# Prefixes rdf4j 2.1.3's Turtle parser resolves without a declaration +# (BasicParserSettings / Namespaces.DEFAULT_RDF4J). +DEFAULT_PREFIXES = { + "cat": "http://www.w3.org/ns/dcat#", "qb": "http://purl.org/linked-data/cube#", + "grddl": "http://www.w3.org/2003/g/data-view#", "ma": "http://www.w3.org/ns/ma-ont#", + "owl": "http://www.w3.org/2002/07/owl#", "rdf": RDF, "rdfa": "http://www.w3.org/ns/rdfa#", + "rdfs": RDFS, "rif": "http://www.w3.org/2007/rif#", "rr": "http://www.w3.org/ns/r2rml#", + "skos": "http://www.w3.org/2004/02/skos/core#", + "skosxl": "http://www.w3.org/2008/05/skos-xl#", "wdr": "http://www.w3.org/2007/05/powder#", + "void": "http://rdfs.org/ns/void#", "wdrs": "http://www.w3.org/2007/05/powder-s#", + "xhv": "http://www.w3.org/1999/xhtml/vocab#", "xml": "http://www.w3.org/XML/1998/namespace", + "xsd": XSD, "prov": "http://www.w3.org/ns/prov#", "sd": "http://www.w3.org/ns/sparql-service-description#", + "org": "http://www.w3.org/ns/org#", "gldp": "http://www.w3.org/ns/people#", + "cnt": "http://www.w3.org/2008/content#", "dcat": "http://www.w3.org/ns/dcat#", + "earl": "http://www.w3.org/ns/earl#", "ht": "http://www.w3.org/2006/http#", + "ptr": "http://www.w3.org/2009/pointers#", "cc": "http://creativecommons.org/ns#", + "ctag": "http://commontag.org/ns#", "dc": DCT, "dc11": "http://purl.org/dc/elements/1.1/", + "dcterms": DCT, "foaf": "http://xmlns.com/foaf/0.1/", "gr": "http://purl.org/goodrelations/v1#", + "ical": "http://www.w3.org/2002/12/cal/icaltzd#", "og": "http://ogp.me/ns#", + "rev": "http://purl.org/stuff/rev#", "sioc": "http://rdfs.org/sioc/ns#", + "v": "http://rdf.data-vocabulary.org/#", "vcard": "http://www.w3.org/2006/vcard/ns#", + "schema": "http://schema.org/", +} + +RESET, RED, YELLOW = "\x1b[0m", "\x1b[31m", "\x1b[33m" + + +# -------------------------------------------------------------------------- +# Output +# -------------------------------------------------------------------------- + +class Out: + def __init__(self, color): + self.color = color + + def failed(self, message): + """The jar's ColoredErrorFormatter line, printed to stdout.""" + if self.color: + print(f"{RED}Validation failed{RESET}: {YELLOW}{message}{RESET}") + else: + print(f"Validation failed: {message}") + + +class JavaCrash(Exception): + """An exception the jar does not catch; Java prints it and exits 1.""" + + +class TurtleError(Exception): + """A Turtle syntax error, as rdf4j's RDFParseException reports it.""" + + def __init__(self, message, line): + super().__init__(f"{message} [line {line}]") + self.message, self.line = message, line + + def log(self): + """rdf4j's ParseErrorLogger line, which logback writes to stderr.""" + stamp = time.strftime("%H:%M:%S") + ".%03d" % (int(time.time() * 1000) % 1000) + print(f"{stamp} [main] ERROR o.e.r.rio.helpers.ParseErrorLogger - [Rio fatal] " + f"{self.message} ({self.line}, -1)", file=sys.stderr) + + def crash(self): + self.log() + return JavaCrash(f"org.eclipse.rdf4j.rio.RDFParseException: {self}") + + +# -------------------------------------------------------------------------- +# RDF model +# -------------------------------------------------------------------------- + +def unescape(s): + def repl(m): + e = m.group(1) + if e[0] in "uU": + return chr(int(e[1:], 16)) + return {"t": "\t", "n": "\n", "r": "\r", "b": "\b", "f": "\f"}.get(e, e) + return re.sub(r"\\(u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8}|.)", repl, s, flags=re.S) + + +NUMERIC = {XSD + t for t in ( + "decimal integer long int short byte double float nonPositiveInteger negativeInteger " + "nonNegativeInteger positiveInteger unsignedLong unsignedInt unsignedShort unsignedByte").split()} +CALENDAR = {XSD + t for t in + "dateTime dateTimeStamp time date gYearMonth gYear gMonthDay gDay gMonth".split()} + + +def jstr(s): + """Sort key for java.lang.String.compareTo (UTF-16 code units).""" + return s.encode("utf-16-be", "surrogatepass") + + +def node_key(n): + """rdf4j LexicalValueComparator: blank nodes, then IRIs, then literals.""" + if n[0] == "bnode": + return (0, jstr(n[1])) + if n[0] == "iri": + return (1, jstr(n[1])) + dt = n[3] if n[3] is not None else RDF + "langString" + group = 0 if dt in NUMERIC else 1 if dt in CALENDAR else 2 + lang = (0, b"") if n[2] is None else (1, jstr(n[2])) + return (2, group, jstr(dt), lang, jstr(n[1])) + + +class Model: + """A repository's statements as rdf4j's MemoryStore returns them. + + The store commits each add through a TreeModel, so statements come back + sorted by subject, predicate and object (LexicalValueComparator), not in + file order; duplicates collapse. + + A node is ("iri", value), ("bnode", id) or ("lit", label, lang, datatype). + """ + + def __init__(self): + self.triples = [] + self._seen = set() + self._bnodes = 0 + + def add(self, s, p, o): + t = (s, p, o) + if t not in self._seen: + self._seen.add(t) + self.triples.append(t) + + def bnode(self): + self._bnodes += 1 + return ("bnode", f"node{self._bnodes}") + + def node(self, term): + """Turn a parsed term into a node, adding the triples it implies. + + rdf4j reports the triples inside `[ ... ]` and `( ... )` before the + triple that uses the blank node, so they are added first. + """ + if term.kind == "iri": + return ("iri", term.value) + if term.kind == "lit": + dt = term.datatype + if term.lang is not None: + return ("lit", unescape(term.value), term.lang.lower(), None) + return ("lit", unescape(term.value), None, dt or XSD + "string") + if term.kind == "bnode": + b = self.bnode() + for p, o in term.value: + on = self.node(o) + self.add(b, ("iri", p.value), on) + return b + if term.kind == "list": + if not term.value: + return ("iri", RDF_NIL) + head = cell = self.bnode() + for k, item in enumerate(term.value): + self.add(cell, ("iri", RDF_FIRST), self.node(item)) + nxt = self.bnode() if k + 1 < len(term.value) else ("iri", RDF_NIL) + self.add(cell, ("iri", RDF_REST), nxt) + cell = nxt + return head + raise ValueError(term.kind) + + def load(self, fp): + for subject, pairs in fp.statements: + s = self.node(subject) + for p, o in pairs: + self.add(s, ("iri", p.value), self.node(o)) + + def index(self): + self.triples.sort(key=lambda t: (node_key(t[0]), node_key(t[1]), node_key(t[2]))) + self.by_sp, self.by_s, self.by_p = {}, {}, {} + for s, p, o in self.triples: + self.by_sp.setdefault((s, p), []).append(o) + self.by_s.setdefault(s, []).append((p, o)) + self.by_p.setdefault(p, []).append((s, o)) + return self + + def objs(self, s, p): + return self.by_sp.get((s, ("iri", p)), []) + + def subjects(self, p, o): + return [s for s, oo in self.by_p.get(("iri", p), []) if oo == ("iri", o)] + + +def string_value(n): + return n[1] + + +def to_string(n): + """rdf4j Value.toString().""" + if n[0] == "iri": + return n[1] + if n[0] == "bnode": + return "_:" + n[1] + if n[2] is not None: + return f'"{n[1]}"@{n[2]}' + return f'"{n[1]}"^^<{n[3]}>' + + +def parse_text(path, text): + """Parse Turtle as rdf4j does; return a FileParse. + + Raises TurtleError; the message is rdf4j's for an undeclared prefix and + this parser's otherwise. + """ + try: + fp = parse_with_datatypes(path, text) + except ic.ParseError as e: + m = re.match(r"line (\d+): (.*)", str(e), re.S) + if m: + raise TurtleError(m.group(2), int(m.group(1))) from e + raise TurtleError(str(e), text.count("\n") + 1) from e + if fp.undeclared: + line, word = fp.undeclared[0] + prefix = word.split(":", 1)[0] + raise TurtleError(f"Namespace prefix '{prefix}' used but not defined", line) + return fp + + +DT_RE = re.compile(r"\^\^(<[^>]*>|[^\s()\[\];,\"'<#]+?)(?=[\s()\[\];,.]|$)") +DT_MARK = "zzdatatype-" + + +def parse_with_datatypes(path, text): + """import_check's parser does not read `^^datatype`. Each datatype is + replaced by a language-tag marker before parsing and restored after.""" + found = [] + + def cut(m): + found.append(m.group(1)) + return f"@{DT_MARK}{len(found) - 1}" + + marked = DT_RE.sub(cut, text) if "^^" in text else text + fp = ic.parse(path, marked, default_prefixes=DEFAULT_PREFIXES) + if found: + restore_datatypes(fp, found) + return fp + + +def restore_datatypes(fp, found): + def walk(t): + if t.kind == "lit" and t.lang and t.lang.startswith(DT_MARK): + dt = found[int(t.lang[len(DT_MARK):])] + if dt.startswith("<"): + t.datatype = dt[1:-1] + else: + pre, local = dt.split(":", 1) + t.datatype = fp.prefixes.get(pre, DEFAULT_PREFIXES.get(pre, "")) + local + t.lang = None + elif t.kind == "list": + for x in t.value: + walk(x) + elif t.kind == "bnode": + for _, o in t.value: + walk(o) + + for subject, pairs in fp.statements: + walk(subject) + for _, o in pairs: + walk(o) + + +def read(path): + with open(path, encoding="utf-8", errors="replace", newline="") as f: + return f.read() + + +# -------------------------------------------------------------------------- +# Inputs +# -------------------------------------------------------------------------- + +def unfold(path): + """Validator.unfoldDirectories: a directory contributes the entries whose + name ends in `.ttl`, in listing order; subdirectories are not entered.""" + if os.path.isdir(path): + out = [] + for name in os.listdir(path): + if name.endswith(".ttl"): + out.extend(unfold(os.path.join(path, name))) + return out + return [path] + + +def java_path_parts(s): + """Components of java.nio.file.Paths.get(s) on Unix.""" + return [c for c in s.split("/") if c] + + +def java_path_str(s): + parts = java_path_parts(s) + return ("/" if s.startswith("/") else "") + "/".join(parts) + + +# -------------------------------------------------------------------------- +# Stage 1: per file +# -------------------------------------------------------------------------- + +PREFIX_RE = re.compile(r"^\s*@prefix\s+([^\s]+):\s+<([^>]+)>\s+\.", re.ASCII) +LINE_SPLIT = re.compile("\r\n|[\n\r

\u0085]") + + +def stage1(out, path, text): + """Return False to stop the run.""" + canonical = os.path.realpath(path) + base = os.path.basename(canonical) + for line in LINE_SPLIT.split(text): + if "@prefix" not in line: + continue + m = PREFIX_RE.search(line) + if not m or not m.group(1).startswith("ogit"): + continue + expected = "http://www.purl.org/" + m.group(1).replace(".", "/") + "/" + if m.group(2) != expected: + source = "co.arago.meerkat.validator.FileDataSource@%x" % (hash(canonical) & 0xFFFFFFF) + msg = (f"Error in validation query {source}: {base} Declared prefix {m.group(1)} " + f"does not match expected ogit convention {expected}. declared as {m.group(2)}") + out.failed(f'Syntax error: "{msg}" at Source: ') + return False + if "ogit.ttl" in canonical: + return True + try: + fp = parse_text(path, text) + except TurtleError as e: + raise e.crash() from e + model = Model() + model.load(fp) + model.index() + subject = next((s for s, _, _ in model.triples if s[0] == "iri"), None) + subject_str = subject[1] if subject else "Optional.empty" + parts = java_path_parts(subject_str) + name = parts[-1] if parts else "" + if name + ".ttl" != base: + print("file name is not same as namespace name") + out.failed(f"File Name mismatch: {base} does not match with name: {name}") + n = len(parts) + if not (2 < n - 1): + raise JavaCrash("java.lang.IllegalArgumentException") + directory = "/".join(parts[2:n - 1]).replace("ogit/", "") + pattern = "/" + directory.lower() + "/(.*)/" + name.lower() + if not re.search(pattern, canonical.lower()): + out.failed(f"Directory mismatch: {base} should be under directory {directory}, " + f"according to {java_path_str(subject_str)}") + return False + return True + + +# -------------------------------------------------------------------------- +# Stage 2: the ten queries +# -------------------------------------------------------------------------- + +def stage2(out, model, show_all): + E, A, V = OGIT + "Entity", OGIT + "Attribute", OGIT + "Verb" + ents = model.subjects(SUBCLASS, E) + atts = model.subjects(SUBPROP, A) + verbs = model.subjects(SUBPROP, V) + members = list(dict.fromkeys(ents + atts + verbs)) + + def has(x, p): + return bool(model.objs(x, p)) + + def missing(x, props): + return " ".join(p for p in props if not has(x, p)) + + def tnames(x): + # BIND(SUBSTR(STR(?t), STRLEN(STR(ogit:)) + 1) AS ?tname) + return [string_value(t)[len(OGIT):] for t in model.objs(x, SUBCLASS) + model.objs(x, SUBPROP)] + + def history_items(x): + """?x ogit:history/rdf:rest*/rdf:first ?h""" + items, seen = [], set() + frontier = list(model.objs(x, OGIT + "history")) + while frontier: + cell = frontier.pop(0) + if cell in seen or cell[0] == "lit": + continue + seen.add(cell) + for f in model.objs(cell, RDF_FIRST): + if f not in items: + items.append(f) + frontier.extend(model.objs(cell, RDF_REST)) + return items + + failures = [] + + def report(desc, n): + failures.append(f"{desc} (/validation{n}.rq)") + + def required(n, kind, xs, props): + bad = [(x, missing(x, props)) for x in xs if missing(x, props)] + for x, miss in (bad if show_all else bad[:1]): + report(f"{kind} {string_value(x)} is missing property(ies): {miss}", n) + + # validation0 / 4 / 5 + v0 = [LABEL, DCT + "description", DCT + "valid", DCT + "creator", OGIT + "scope", + OGIT + "parent", OGIT + "mandatory-attributes", OGIT + "optional-attributes", OGIT + "allowed"] + base = [LABEL, DCT + "description", DCT + "valid", DCT + "creator"] + results = {} + required(0, "Entity", ents, v0) + results[0] = list(failures) + failures.clear() + + # validation1: history items missing properties + hprops = [DCT + "identifier", DCT + "description", DCT + "creator", DCT + "date"] + for x in members: + for h in history_items(x): + miss = " ".join(p for p in hprops if not (h[0] != "lit" and model.objs(h, p))) + if miss: + for t in tnames(x)[:1] if not show_all else tnames(x): + report(f"{t} {string_value(x)} is missing property(ies) in one of its " + f"ogit:history items: {miss}", 1) + break + if failures and not show_all: + break + results[1] = list(failures) + failures.clear() + + # validation2: duplicate history identifiers + for x in members: + counts = {} + for h in history_items(x): + if h[0] == "lit": + continue + for i in model.objs(h, DCT + "identifier"): + counts[i] = counts.get(i, 0) + 1 + for i, c in counts.items(): + if c > 1: + for t in tnames(x)[:1] if not show_all else tnames(x): + suffix = f" ({string_value(i)})" if i[0] == "lit" else "" + report(f"{t} {string_value(x)} has duplicate dcterms:identifier in its " + f"ogit:history items{suffix}", 2) + if failures and not show_all: + break + results[2] = list(failures) + failures.clear() + + # validation3: date format + date_re = re.compile(r"\d{4}-\d{2}-\d{2}") + for x in members: + ds = model.objs(x, DCT + "created") + model.objs(x, DCT + "modified") + for h in history_items(x): + if h[0] != "lit": + ds += model.objs(h, DCT + "date") + if any(not date_re.fullmatch(string_value(d)) for d in ds): + for t in tnames(x)[:1] if not show_all else tnames(x): + report(f"{t} {string_value(x)} has invalid value for dcterms:created/modified/date: " + f"Should be in ISO 8601 format (YYYY-MM-DD)", 3) + if failures and not show_all: + break + results[3] = list(failures) + failures.clear() + + required(4, "Attribute", atts, base) + results[4] = list(failures) + failures.clear() + required(5, "Verb", verbs, base) + results[5] = list(failures) + failures.clear() + + # validation6..9: the subquery picks one subject among ALL subjects + # (LIMIT 1), then the outer query keeps it only if it is an attribute. + att_set = set(atts) + vt, vp = OGIT + "validation-type", OGIT + "validation-parameter" + all_subjects = list(dict.fromkeys(s for s, _, _ in model.triples)) + + def empty_lit(n): + return n[0] == "lit" and n[1] == "" and n[2] is None and n[3] == XSD + "string" + + def limited(n, cands, outer, desc): + picks = cands if show_all else cands[:1] + for x in picks: + if outer(x): + report(desc(x), n) + + limited(6, [x for x in all_subjects if has(x, vt) and not has(x, vp)], + lambda x: x in att_set, + lambda x: f"Attribute {string_value(x)} is missing property(ies): {vp}") + results[6] = list(failures) + failures.clear() + limited(7, [x for x in all_subjects if any(empty_lit(o) for o in model.objs(x, vp))], + lambda x: x in att_set and has(x, vt), + lambda x: f"Attribute {string_value(x)} has empty value for ogit:validation-parameter(ies): ") + results[7] = list(failures) + failures.clear() + limited(8, [x for x in all_subjects if any(empty_lit(o) for o in model.objs(x, vt))], + lambda x: x in att_set and has(x, vp), + lambda x: f"Attribute {string_value(x)} has empty value for ogit:validation-type(ies): ") + results[8] = list(failures) + failures.clear() + limited(9, [x for x in all_subjects if has(x, vp) and not has(x, vt)], + lambda x: x in att_set, + lambda x: f"Attribute {string_value(x)} is missing property(ies): {vt}") + results[9] = list(failures) + failures.clear() + + lines = [line for n in range(10) for line in results[n]] + for line in lines: + out.failed(line) + return not lines + + +# -------------------------------------------------------------------------- +# Stage 3: the entity registry (TTLLoader + OgitValidator) +# -------------------------------------------------------------------------- + +class RegistryError(Exception): + pass + + +def stage3(model): + """Return (errors, warnings), or raise RegistryError with the loader's message.""" + def first(s, p): + o = model.objs(s, p) + return string_value(o[0]) if o else None + + def first_resource(s, p): + for o in model.objs(s, p): + if o[0] != "lit": + return o + return None + + def as_values(head): + if head is None: + raise RegistryError("list head may not be null") + vals, cell, seen = [], head, set() + while cell != ("iri", RDF_NIL): + if cell in seen: + raise RegistryError("list is cyclic") + seen.add(cell) + f = model.objs(cell, RDF_FIRST) + r = model.objs(cell, RDF_REST) + if not f or not r: + raise RegistryError(f"incomplete list at {to_string(cell)}") + vals.append(f[0]) + cell = r[0] + if cell[0] == "lit": + raise RegistryError("list rest is not a resource") + return vals + + def attributes(s, p): + vals = as_values(first_resource(s, p)) + return [to_string(v) for v in vals] or None + + def parse_date(value, which): + if value is None: + raise RegistryError("null") + found = "" + for m in re.finditer(which + r"=(.*?);", value): + found = m.group(1) + if found == "": + return None + return True if re.match(r"\s*[+-]?\d+-[+-]?\d+-[+-]?\d+", found) else None + + def common(s): + valid = first(s, DCT + "valid") + return { + "id": string_value(s), + "name": first(s, LABEL), + "description": first(s, DCT + "description"), + "valid-from": parse_date(valid, "start"), + "valid-until": parse_date(valid, "end"), + "creator": first(s, DCT + "creator"), + } + + def allowed(s): + head = first_resource(s, OGIT + "allowed") + if head is None: + return None + out = [] + for v in as_values(head): + if v[0] == "lit": + raise RegistryError("class cast") + pairs = model.by_s.get(v, []) + if not pairs: + raise RegistryError(None) + out.append({"with": to_string(pairs[0][0]), "to": string_value(pairs[0][1])}) + return out + + entities, attrs, verbs = {}, {}, {} + for s in model.subjects(SUBCLASS, OGIT + "Entity"): + item = common(s) + item["scope"] = first(s, OGIT + "scope") + item["parent"] = first(s, OGIT + "parent") + optional = attributes(s, OGIT + "optional-attributes") + mandatory = attributes(s, OGIT + "mandatory-attributes") + attributes(s, OGIT + "indexed-attributes") + item["mandatory"], item["optional"] = mandatory, optional + item["allowed"] = allowed(s) + entities[to_string(s)] = item + for s in model.subjects(SUBPROP, OGIT + "Attribute"): + attrs[to_string(s)] = common(s) + for s in model.subjects(SUBPROP, OGIT + "Verb"): + item = common(s) + item["cardinality"] = first(s, OGIT + "cardinality") + verbs[to_string(s)] = item + + errors = [] + + def blank(v): + return v is None or v.strip(" \t\n\r\x0b\x0c" + "".join(map(chr, range(0, 33)))) == "" + + def base(kind, items): + for item in items.values(): + i = item["id"] + if blank(item["description"]): + errors.append(f"{kind} id: {i} , description missing!") + if blank(item["name"]): + errors.append(f"{kind} id: {i} , label missing!") + if item["valid-from"] is None: + errors.append(f"{kind} id: {i} , valid from missing!") + if blank(item["creator"]): + errors.append(f"{kind} id: {i} , creator missing!") + + def j(v): + return "null" if v is None else v + + base("entities", entities) + for item in entities.values(): + i = item["id"] + scope = item["scope"] + if scope is not None and scope.lower() not in ("sgo", "nto"): + errors.append(f"type id: {i} , unknown scope {scope}") + parent = item["parent"] + if parent is not None: + if parent not in entities: + errors.append(f"type id: {i} , parent id : {parent} doesn't exist!") + elif i.lower() != (OGIT + "Node").lower(): + errors.append(f"type id: {i} , parent id missing!") + for key in ("mandatory", "optional"): + for name in item[key] or []: + if name not in attrs: + errors.append(f"type id: {i} , attribute id : {name} doesn't exist!") + for edge in item["allowed"] or []: + if edge["to"] not in entities: + errors.append(f"edge id: {i} , head connection id : {j(edge['to'])} doesn't exist!") + if edge["with"] not in verbs: + errors.append(f"edge id: {i} , connection id e: {j(edge['with'])} doesn't exist!") + base("verbs", verbs) + for item in verbs.values(): + card = item["cardinality"] + if card is not None and card.lower() not in ("one2one", "one2many", "many2one", "many2many"): + errors.append(f"edge id: {item['id']} , unknown cardinality {card}") + base("attributes", attrs) + return errors, [] + + +# -------------------------------------------------------------------------- +# Main +# -------------------------------------------------------------------------- + +def default_files(): + out = [] + for root, dirs, files in os.walk("."): + dirs.sort() + for f in sorted(files): + if f.endswith(".ttl") and not f.startswith("graphit-ontology."): + out.append(os.path.join(root, f)) + return out + + +def validate(paths, color=True, show_all=False): + out = Out(color) + files = [] + for p in paths: + files.extend(unfold(p)) + if not files: + out.failed("No input files specified") + return 1 + texts = {} + try: + for f in files: + if not os.path.exists(f): + out.failed(f"IO Error: {f}") + return 1 + texts[f] = read(f) + if len(files) > 1: + for f in files: + if not stage1(out, f, texts[f]): + return 1 + model = Model() + failed = False + for f in files: + try: + model.load(parse_text(f, texts[f])) + except TurtleError as e: + e.log() + out.failed(f'Syntax error: "{e}" at Source: {os.path.realpath(f)} ' + f"Line: {e.line} Column: -1 ") + failed = True + if failed: + return 1 + model.index() + if not stage2(out, model, show_all): + return 1 + # Stage 3 reads all files as one concatenated stream. + registry = Model() + try: + registry.load(parse_concatenated(files, texts)) + registry.index() + errors, warnings = stage3(registry) + except (RegistryError, TurtleError) as e: + if isinstance(e, TurtleError): + e.log() + msg = str(e) + print(f"Error while loading entity registry: {msg}", file=sys.stderr) + # The caller then reads the registry's result map (MV@334). + raise JavaCrash('java.lang.NullPointerException: Cannot invoke ' + '"java.util.Map.get(Object)" because "ontMap" is null') from e + except JavaCrash as e: + print(f'Exception in thread "main" {e}', file=sys.stderr) + return 1 + if errors: + print(f"Count of errors: {len(errors)}", file=sys.stderr) + print(f"Count of warnings: {len(warnings)}", file=sys.stderr) + for e in errors: + print(f"ERROR: {e}", file=sys.stderr) + for w in warnings: + print(f"WARNING: {w}", file=sys.stderr) + return 1 + if warnings: + print(f"Count of warnings: {len(warnings)}", file=sys.stderr) + for w in warnings: + print(f"WARNING: {w}", file=sys.stderr) + print("Validation successful.") + return 0 + + +def parse_concatenated(files, texts): + # SequenceInputStream adds no separator between files. + return parse_text("", "".join(texts[f] for f in files)) + + +def main(argv=None): + ap = argparse.ArgumentParser(description=__doc__.split("\n\n")[0]) + ap.add_argument("files", nargs="*", help="TTL files or directories (default: as validate.sh)") + ap.add_argument("--no-color", action="store_true", help="omit the ANSI colours the jar prints") + ap.add_argument("--all", action="store_true", + help="stage 2: list every failing subject, not one per query") + args = ap.parse_args(argv) + files = args.files or default_files() + sys.stdout.reconfigure(line_buffering=True) + return validate(files, color=not args.no_color, show_all=args.all) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/test_import_check.py b/tools/test_import_check.py new file mode 100644 index 00000000000..4e8f89ebeb8 --- /dev/null +++ b/tools/test_import_check.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +"""Tests for tools/import_check.py. Run: python3 -I tools/test_import_check.py""" + +import os +import sys +import tempfile +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import import_check # noqa: E402 + +HEAD = """@prefix ogit: . +@prefix ogit.T: . +@prefix rdfs: . +@prefix owl: . +""" + +BASE = { + "ogit.ttl": HEAD + """ +ogit:Entity a rdfs:Class . +ogit:Verb a rdfs:Class . +""", + "T/entities/A.ttl": HEAD + """ +ogit.T:A a rdfs:Class ; rdfs:subClassOf ogit:Entity ; + rdfs:label "A" ; ogit:description \"\"\"says \\"x\\"\"\"\" ; + ogit:allowed ( [ ogit.T:links ogit.T:B ] ) . +""", + "T/entities/B.ttl": HEAD + """ +ogit.T:B a rdfs:Class ; rdfs:subClassOf ogit.T:A . +""", + "T/verbs/links.ttl": HEAD + """ +ogit.T:links a owl:ObjectProperty ; rdfs:subPropertyOf ogit:Verb . +""", +} + + +def run(overrides): + files = dict(BASE) + files.update(overrides) + with tempfile.TemporaryDirectory() as root: + paths = [] + for rel, text in files.items(): + if text is None: + continue + path = os.path.join(root, rel) + os.makedirs(os.path.dirname(path), exist_ok=True) + with open(path, "w", encoding="utf-8") as fh: + fh.write(text) + paths.append(path) + model, findings = import_check.run(root, sorted(paths)) + return model, sorted({f[0] for f in findings}) + + +class ImportCheck(unittest.TestCase): + def test_clean_repository_has_no_findings(self): + model, codes = run({}) + self.assertEqual(codes, []) + self.assertEqual(len(model["edges"]), 1) + self.assertEqual(len(model["entities"]), 2) + + def test_c01_undeclared_prefix(self): + _, codes = run({"T/entities/C.ttl": HEAD + "ogit.T:C a rdfs:Class ; rdfs:subClassOf ogit:Entity ; ogit:allowed ( [ ogit.T:links ogit.X:Y ] ) .\n"}) + self.assertIn("C01", codes) + + def test_c02_class_shaped_verb_without_from_to(self): + _, codes = run({"T/verbs/links.ttl": HEAD + "ogit.T:links a rdfs:Class ; rdfs:subClassOf ogit:Verb .\n"}) + self.assertEqual(codes, ["C02"]) + + def test_c02_property_verb_must_be_an_object_property(self): + _, codes = run({"T/verbs/links.ttl": HEAD + "ogit.T:links a rdfs:Property ; rdfs:subPropertyOf ogit:Verb .\n"}) + self.assertEqual(codes, ["C02"]) + + def test_class_verb_with_from_to_is_supported(self): + verb = HEAD + "ogit.T:links a rdfs:Class ; rdfs:subClassOf ogit:Verb ; ogit:from-to ( [ ogit:from ogit.T:A ; ogit:to ogit.T:B ] ) .\n" + _, codes = run({"T/verbs/links.ttl": verb}) + self.assertEqual(codes, []) + + def test_c03_file_name_case(self): + _, codes = run({"T/verbs/links.ttl": None, + "T/verbs/Links.ttl": BASE["T/verbs/links.ttl"]}) + self.assertEqual(codes, ["C03"]) + + def test_c04_undeclared_verb(self): + _, codes = run({"T/verbs/links.ttl": None}) + self.assertEqual(codes, ["C04"]) + + def test_c05_target_is_not_an_entity(self): + _, codes = run({"T/entities/B.ttl": None}) + self.assertEqual(codes, ["C05"]) + + def test_c06_malformed_cell(self): + _, codes = run({"T/entities/A.ttl": HEAD + "ogit.T:A a rdfs:Class ; rdfs:subClassOf ogit:Entity ; ogit:allowed ( ogit.T:links ) .\n"}) + self.assertIn("C06", codes) + + def test_c07_duplicate_declaration(self): + _, codes = run({"T/entities/B2.ttl": HEAD + "ogit.T:B a rdfs:Class ; rdfs:subClassOf ogit.T:A .\n"}) + self.assertIn("C07", codes) + + def test_c08_class_off_the_tree(self): + _, codes = run({"T/entities/E.ttl": HEAD + "ogit.T:E a rdfs:Class ; rdfs:subClassOf ogit:Enumeration .\n"}) + self.assertEqual(codes, ["C08"]) + + def test_from_to_edges_are_collected_and_checked(self): + verb = HEAD + "ogit.T:has a rdfs:Class ; rdfs:subClassOf ogit:Verb ; ogit:from-to ( [ ogit:from ogit.T:A ; ogit:to ogit.T:Z ] ) .\n" + model, codes = run({"T/verbs/has.ttl": verb}) + self.assertEqual(codes, ["C05"]) + self.assertEqual(sum(1 for e in model["edges"] if e[5] == "from-to"), 1) + + def test_parse_error_is_reported_not_raised(self): + model, codes = run({"T/entities/B.ttl": HEAD + "ogit.T:B a rdfs:Class ; rdfs:label \"open\n"}) + self.assertIsNone(model) + self.assertEqual(codes, ["PARSE"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/test_ogit_validate.py b/tools/test_ogit_validate.py new file mode 100644 index 00000000000..fd5f6cab70d --- /dev/null +++ b/tools/test_ogit_validate.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python3 +"""Tests for ogit_validate.py. + +Each case is a small set of TTL files. Its expected exit status, stdout and +stderr were recorded from bin/ogit-validator.jar and are kept in +testdata/ogit_validate_expected.json; the port must print the same. Paths, +the time of day and the jar's object hash are normalised. + + python3 -I tools/test_ogit_validate.py + python3 -I tools/test_ogit_validate.py --record # rewrite the expectations from the jar (needs java) + +When java is installed, the cases also run against the jar directly. +""" + +import json +import os +import re +import shutil +import subprocess +import sys +import tempfile +import unittest + +HERE = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, HERE) +import ogit_validate as ov # noqa: E402 + +REPO = os.path.dirname(HERE) +JAR = os.path.join(REPO, "bin", "ogit-validator.jar") +EXPECTED = os.path.join(HERE, "testdata", "ogit_validate_expected.json") + +# rdf4j's wording for a generic Turtle syntax error is not reproduced (see +# the module docstring); only the quoted message differs. +SYNTAX_TEXT_DIFFERS = {"ogit_ttl_skipped"} + +P = """@prefix ogit: . +@prefix ogit.X: . +@prefix rdfs: . +@prefix dcterms: . +""" + +def ent(name, extra="", scope='"NTO"', parent="ogit:Node", mand="( )", opt="( )", allowed="( )", + valid='"start=2020-01-01;"', desc='"d"', creator='"c"', ns="ogit.X"): + body = [f"{ns}:{name} a rdfs:Class", "rdfs:subClassOf ogit:Entity", f'rdfs:label "{name}"'] + if desc is not None: body.append(f"dcterms:description {desc}") + if valid is not None: body.append(f"dcterms:valid {valid}") + if creator is not None: body.append(f"dcterms:creator {creator}") + if scope is not None: body.append(f"ogit:scope {scope}") + if parent is not None: body.append(f"ogit:parent {parent}") + if mand is not None: body.append(f"ogit:mandatory-attributes {mand}") + if opt is not None: body.append(f"ogit:optional-attributes {opt}") + body.append("ogit:indexed-attributes ( )") + if allowed is not None: body.append(f"ogit:allowed {allowed}") + if extra: body.append(extra) + return P + "\n" + " ;\n ".join(body) + " .\n" + +def prop(kind, name, extra="", ns="ogit.X", valid='"start=2020-01-01;"'): + body = [f"{ns}:{name} a rdf:Property", f"rdfs:subPropertyOf ogit:{kind}", f'rdfs:label "{name}"', + 'dcterms:description "d"', 'dcterms:creator "c"'] + if valid is not None: body.append(f"dcterms:valid {valid}") + if extra: body.append(extra) + return P + "@prefix rdf: .\n\n" + " ;\n ".join(body) + " .\n" + +NODE = ent("Node", ns="ogit", scope='"SGO"', parent="ogit:Node") +BASE = {"OGIT/SGO/sgo/entities/Node.ttl": NODE} + +CASES = { + "ok": {**BASE, "OGIT/NTO/X/entities/A.ttl": ent("A", mand="( ogit.X:c )", allowed="( [ ogit.X:v ogit.X:A ] )"), + "OGIT/NTO/X/attributes/c.ttl": prop("Attribute", "c"), + "OGIT/NTO/X/verbs/v.ttl": prop("Verb", "v", 'ogit:cardinality "one2many"')}, + "node_outside_ogit_dir": {"repo/SGO/sgo/entities/Node.ttl": NODE, "repo/NTO/X/entities/A.ttl": ent("A")}, + "prefix_mismatch": {**BASE, "OGIT/NTO/X/entities/A.ttl": ent("A").replace("", "")}, + "file_name_mismatch": {**BASE, "OGIT/NTO/X/entities/B.ttl": ent("A")}, + "directory_mismatch": {**BASE, "OGIT/NTO/Y/entities/A.ttl": ent("A")}, + "undeclared_prefix": {**BASE, "OGIT/NTO/X/entities/A.ttl": ent("A", extra="ogit.Q:foo ogit:Node")}, + "single_file": {"OGIT/NTO/Y/entities/B.ttl": ent("A", desc=None)}, + "stage2_each_query": {**BASE, + "OGIT/NTO/X/entities/A.ttl": ent("A", creator=None, extra='dcterms:created "2020-1-1"'), + "OGIT/NTO/X/attributes/c.ttl": prop("Attribute", "c", valid=None), + "OGIT/NTO/X/verbs/v.ttl": prop("Verb", "v", valid=None)}, + "stage2_validation_params": {**BASE, + "OGIT/NTO/X/attributes/a6.ttl": prop("Attribute", "a6", 'ogit:validation-type "regex"'), + "OGIT/NTO/X/attributes/a7.ttl": prop("Attribute", "a7", 'ogit:validation-type "regex" ; ogit:validation-parameter ""')}, + "stage2_validation_params_b": {**BASE, + "OGIT/NTO/X/attributes/a8.ttl": prop("Attribute", "a8", 'ogit:validation-parameter "x" ; ogit:validation-type ""')}, + "stage2_validation_params_c": {**BASE, + "OGIT/NTO/X/attributes/a9.ttl": prop("Attribute", "a9", 'ogit:validation-parameter "x"')}, + "stage2_history": {**BASE, + "OGIT/NTO/X/entities/A.ttl": ent("A", extra='ogit:history ( [ dcterms:identifier "1" ; dcterms:date "2020-01-01" ] [ dcterms:identifier "1" ; dcterms:description "d" ; dcterms:creator "c" ; dcterms:date "2020-13" ] )')}, + "stage3_errors": {**BASE, + "OGIT/NTO/X/entities/A.ttl": ent("A", scope='"XYZ"', parent="ogit.X:Missing", mand="( ogit.X:nope )", + opt="( ogit.X:c ogit.X:gone )", allowed="( [ ogit.X:v ogit.X:Nowhere ] [ ogit.X:noverb ogit.X:A ] )", + desc='" "', valid='"start=;"'), + "OGIT/NTO/X/entities/B.ttl": ent("B", parent='"http://www.purl.org/ogit/X/A"', scope='"sgo"'), + "OGIT/NTO/X/attributes/c.ttl": prop("Attribute", "c"), + "OGIT/NTO/X/verbs/v.ttl": prop("Verb", "v", 'ogit:cardinality "lots"')}, + "stage3_order": {**BASE, + "OGIT/SGO/sgo/entities/Abc.ttl": ent("Abc", ns="ogit", scope='"BAD"'), + "OGIT/NTO/Q/entities/Z.ttl": ent("Z", ns="ogit.Q", scope='"BAD"').replace("ogit.X:", "ogit.Q:").replace("", ""), + "OGIT/NTO/X/entities/M.ttl": ent("M", scope='"BAD"')}, + "stage3_list_head_null": {**BASE, "OGIT/NTO/X/entities/A.ttl": ent("A", mand='"none"')}, + "ogit_ttl_skipped": {**BASE, "OGIT/ogit.ttl": P + "\nthis is not turtle .\n", "OGIT/NTO/X/entities/A.ttl": ent("A")}, + "stage2_syntax_error_single": {"OGIT/NTO/X/entities/A.ttl": ent("A").replace("ogit:Node", "ogit.Q:Node")}, + "stage3_concat_glue": {**BASE, "OGIT/NTO/X/entities/A.ttl": ent("A").rstrip("\n") + " # no newline", "OGIT/NTO/X/entities/Z.ttl": "ogit.X:Z rdfs:label \"z\" .\n" + ent("B")}, + "no_ogit_prefix_ok_glue": {**BASE, "OGIT/NTO/X/entities/A.ttl": ent("A").rstrip("\n") + " # trailing comment, no newline"}, +} + + +def norm(text, root): + out = [] + for line in text.splitlines(): + if "JAVA_TOOL_OPTIONS" in line or line.startswith("\tat ") or line.startswith("\t... "): + continue + line = re.sub(r"^\d\d:\d\d:\d\d\.\d{3} ", "TIME ", line) + line = re.sub(r"FileDataSource@[0-9a-f]+", "FileDataSource@H", line) + out.append(line.replace(os.path.realpath(root), "")) + return out + + +def run_case(files, command): + d = tempfile.mkdtemp(prefix="ogit-validate-") + try: + for rel, text in files.items(): + p = os.path.join(d, rel) + os.makedirs(os.path.dirname(p), exist_ok=True) + with open(p, "w", encoding="utf-8") as f: + f.write(text) + root = os.path.join(d, next(iter(files)).split("/")[0]) + args = sorted("./" + os.path.relpath(os.path.join(dp, f), root) + for dp, _, fs in os.walk(root) for f in fs) + r = subprocess.run(command + args, cwd=root, capture_output=True, text=True) + return [r.returncode, norm(r.stdout, root), norm(r.stderr, root)] + finally: + shutil.rmtree(d) + + +PORT = [sys.executable, "-I", "-B", os.path.join(HERE, "ogit_validate.py")] + + +def quoted_message_removed(result): + return [result[0], [re.sub(r'Syntax error: "[^"]*"', 'Syntax error: "…"', x) for x in result[1]], + [re.sub(r"\[Rio fatal\] .*", "[Rio fatal] …", x) for x in result[2]]] + + +class Cases(unittest.TestCase): + @classmethod + def setUpClass(cls): + with open(EXPECTED, encoding="utf-8") as f: + cls.expected = json.load(f) + + def test_every_case_has_an_expectation(self): + self.assertEqual(sorted(CASES), sorted(self.expected)) + + def test_port_matches_recorded_jar_output(self): + for name, files in CASES.items(): + with self.subTest(name): + got, want = run_case(files, PORT), self.expected[name] + if name in SYNTAX_TEXT_DIFFERS: + got, want = quoted_message_removed(got), quoted_message_removed(want) + self.assertEqual(got, want) + + @unittest.skipUnless(shutil.which("java") and os.path.exists(JAR), "needs java and the jar") + def test_recorded_output_is_still_the_jars(self): + for name in ("ok", "directory_mismatch", "stage3_errors"): + with self.subTest(name): + self.assertEqual(run_case(CASES[name], ["java", "-jar", JAR]), self.expected[name]) + + +class Order(unittest.TestCase): + def test_store_order_is_lexical(self): + nodes = [("lit", "a", None, ov.XSD + "string"), ("iri", "http://b"), ("bnode", "x"), + ("iri", "http://a"), ("lit", "a", "en", None), ("lit", "1", None, ov.XSD + "int")] + got = sorted(nodes, key=ov.node_key) + self.assertEqual(got, [("bnode", "x"), ("iri", "http://a"), ("iri", "http://b"), + ("lit", "1", None, ov.XSD + "int"), ("lit", "a", "en", None), + ("lit", "a", None, ov.XSD + "string")]) + + def test_java_string_order_is_utf16(self): + # U+FFFF sorts after U+10000 in UTF-16, before it by code point. + self.assertLess(ov.jstr("\U00010000"), ov.jstr("\uffff")) + + +class AllFlag(unittest.TestCase): + def test_all_lists_every_failing_subject(self): + files = dict(BASE) + for n in ("A", "B", "C"): + files[f"OGIT/NTO/X/entities/{n}.ttl"] = ent(n, desc=None) + one = run_case(files, PORT) + every = run_case(files, PORT + ["--all"]) + self.assertEqual(len([x for x in one[1] if "validation0" in x]), 1) + self.assertEqual(len([x for x in every[1] if "validation0" in x]), 3) + + +def record(): + out = {name: run_case(files, ["java", "-jar", JAR]) for name, files in CASES.items()} + with open(EXPECTED, "w", encoding="utf-8") as f: + json.dump(out, f, indent=1) + f.write("\n") + print(f"recorded {len(out)} cases in {EXPECTED}") + + +if __name__ == "__main__": + if sys.argv[1:] == ["--record"]: + record() + else: + unittest.main() diff --git a/tools/testdata/ogit_validate_expected.json b/tools/testdata/ogit_validate_expected.json new file mode 100644 index 00000000000..06d4f3a079e --- /dev/null +++ b/tools/testdata/ogit_validate_expected.json @@ -0,0 +1,164 @@ +{ + "ok": [ + 0, + [ + "Validation successful." + ], + [] + ], + "node_outside_ogit_dir": [ + 1, + [ + "\u001b[31mValidation failed\u001b[0m: \u001b[33mDirectory mismatch: Node.ttl should be under directory ogit, according to http:/www.purl.org/ogit/Node\u001b[0m" + ], + [] + ], + "prefix_mismatch": [ + 1, + [ + "\u001b[31mValidation failed\u001b[0m: \u001b[33mSyntax error: \"Error in validation query co.arago.meerkat.validator.FileDataSource@H: A.ttl Declared prefix ogit.X does not match expected ogit convention http://www.purl.org/ogit/X/. declared as http://www.purl.org/ogit/Y/\" at Source: \u001b[0m" + ], + [] + ], + "file_name_mismatch": [ + 1, + [ + "file name is not same as namespace name", + "\u001b[31mValidation failed\u001b[0m: \u001b[33mFile Name mismatch: B.ttl does not match with name: A\u001b[0m", + "\u001b[31mValidation failed\u001b[0m: \u001b[33mDirectory mismatch: B.ttl should be under directory X, according to http:/www.purl.org/ogit/X/A\u001b[0m" + ], + [] + ], + "directory_mismatch": [ + 1, + [ + "\u001b[31mValidation failed\u001b[0m: \u001b[33mDirectory mismatch: A.ttl should be under directory X, according to http:/www.purl.org/ogit/X/A\u001b[0m" + ], + [] + ], + "undeclared_prefix": [ + 1, + [], + [ + "TIME [main] ERROR o.e.r.rio.helpers.ParseErrorLogger - [Rio fatal] Namespace prefix 'ogit.Q' used but not defined (18, -1)", + "Exception in thread \"main\" org.eclipse.rdf4j.rio.RDFParseException: Namespace prefix 'ogit.Q' used but not defined [line 18]" + ] + ], + "single_file": [ + 1, + [ + "\u001b[31mValidation failed\u001b[0m: \u001b[33mEntity http://www.purl.org/ogit/X/A is missing property(ies): http://purl.org/dc/terms/description (/validation0.rq)\u001b[0m" + ], + [] + ], + "stage2_each_query": [ + 1, + [ + "\u001b[31mValidation failed\u001b[0m: \u001b[33mEntity http://www.purl.org/ogit/X/A is missing property(ies): http://purl.org/dc/terms/creator (/validation0.rq)\u001b[0m", + "\u001b[31mValidation failed\u001b[0m: \u001b[33mEntity http://www.purl.org/ogit/X/A has invalid value for dcterms:created/modified/date: Should be in ISO 8601 format (YYYY-MM-DD) (/validation3.rq)\u001b[0m", + "\u001b[31mValidation failed\u001b[0m: \u001b[33mAttribute http://www.purl.org/ogit/X/c is missing property(ies): http://purl.org/dc/terms/valid (/validation4.rq)\u001b[0m", + "\u001b[31mValidation failed\u001b[0m: \u001b[33mVerb http://www.purl.org/ogit/X/v is missing property(ies): http://purl.org/dc/terms/valid (/validation5.rq)\u001b[0m" + ], + [] + ], + "stage2_validation_params": [ + 1, + [ + "\u001b[31mValidation failed\u001b[0m: \u001b[33mAttribute http://www.purl.org/ogit/X/a6 is missing property(ies): http://www.purl.org/ogit/validation-parameter (/validation6.rq)\u001b[0m", + "\u001b[31mValidation failed\u001b[0m: \u001b[33mAttribute http://www.purl.org/ogit/X/a7 has empty value for ogit:validation-parameter(ies): (/validation7.rq)\u001b[0m" + ], + [] + ], + "stage2_validation_params_b": [ + 1, + [ + "\u001b[31mValidation failed\u001b[0m: \u001b[33mAttribute http://www.purl.org/ogit/X/a8 has empty value for ogit:validation-type(ies): (/validation8.rq)\u001b[0m" + ], + [] + ], + "stage2_validation_params_c": [ + 1, + [ + "\u001b[31mValidation failed\u001b[0m: \u001b[33mAttribute http://www.purl.org/ogit/X/a9 is missing property(ies): http://www.purl.org/ogit/validation-type (/validation9.rq)\u001b[0m" + ], + [] + ], + "stage2_history": [ + 1, + [ + "\u001b[31mValidation failed\u001b[0m: \u001b[33mEntity http://www.purl.org/ogit/X/A is missing property(ies) in one of its ogit:history items: http://purl.org/dc/terms/description http://purl.org/dc/terms/creator (/validation1.rq)\u001b[0m", + "\u001b[31mValidation failed\u001b[0m: \u001b[33mEntity http://www.purl.org/ogit/X/A has duplicate dcterms:identifier in its ogit:history items (1) (/validation2.rq)\u001b[0m", + "\u001b[31mValidation failed\u001b[0m: \u001b[33mEntity http://www.purl.org/ogit/X/A has invalid value for dcterms:created/modified/date: Should be in ISO 8601 format (YYYY-MM-DD) (/validation3.rq)\u001b[0m" + ], + [] + ], + "stage3_errors": [ + 1, + [], + [ + "Count of errors: 9", + "Count of warnings: 0", + "ERROR: entities id: http://www.purl.org/ogit/X/A , description missing!", + "ERROR: entities id: http://www.purl.org/ogit/X/A , valid from missing!", + "ERROR: type id: http://www.purl.org/ogit/X/A , unknown scope XYZ", + "ERROR: type id: http://www.purl.org/ogit/X/A , parent id : http://www.purl.org/ogit/X/Missing doesn't exist!", + "ERROR: type id: http://www.purl.org/ogit/X/A , attribute id : http://www.purl.org/ogit/X/nope doesn't exist!", + "ERROR: type id: http://www.purl.org/ogit/X/A , attribute id : http://www.purl.org/ogit/X/gone doesn't exist!", + "ERROR: edge id: http://www.purl.org/ogit/X/A , head connection id : http://www.purl.org/ogit/X/Nowhere doesn't exist!", + "ERROR: edge id: http://www.purl.org/ogit/X/A , connection id e: http://www.purl.org/ogit/X/noverb doesn't exist!", + "ERROR: edge id: http://www.purl.org/ogit/X/v , unknown cardinality lots" + ] + ], + "stage3_order": [ + 1, + [], + [ + "Count of errors: 3", + "Count of warnings: 0", + "ERROR: type id: http://www.purl.org/ogit/Abc , unknown scope BAD", + "ERROR: type id: http://www.purl.org/ogit/Q/Z , unknown scope BAD", + "ERROR: type id: http://www.purl.org/ogit/X/M , unknown scope BAD" + ] + ], + "stage3_list_head_null": [ + 1, + [], + [ + "Error while loading entity registry: list head may not be null", + "Exception in thread \"main\" java.lang.NullPointerException: Cannot invoke \"java.util.Map.get(Object)\" because \"ontMap\" is null" + ] + ], + "ogit_ttl_skipped": [ + 1, + [ + "\u001b[31mValidation failed\u001b[0m: \u001b[33mSyntax error: \"Expected ':', found ' ' [line 6]\" at Source: /ogit.ttl Line: 6 Column: -1 \u001b[0m" + ], + [ + "TIME [main] ERROR o.e.r.rio.helpers.ParseErrorLogger - [Rio fatal] Expected ':', found ' ' (6, -1)" + ] + ], + "stage2_syntax_error_single": [ + 1, + [ + "\u001b[31mValidation failed\u001b[0m: \u001b[33mSyntax error: \"Namespace prefix 'ogit.Q' used but not defined [line 13]\" at Source: /NTO/X/entities/A.ttl Line: 13 Column: -1 \u001b[0m" + ], + [ + "TIME [main] ERROR o.e.r.rio.helpers.ParseErrorLogger - [Rio fatal] Namespace prefix 'ogit.Q' used but not defined (13, -1)" + ] + ], + "stage3_concat_glue": [ + 1, + [], + [ + "TIME [main] ERROR o.e.r.rio.helpers.ParseErrorLogger - [Rio fatal] Namespace prefix 'ogit.X' used but not defined (1, -1)", + "Exception in thread \"main\" org.eclipse.rdf4j.rio.RDFParseException: Namespace prefix 'ogit.X' used but not defined [line 1]" + ] + ], + "no_ogit_prefix_ok_glue": [ + 0, + [ + "Validation successful." + ], + [] + ] +}