org.hl7.fhir.r5.renderers.utils.ElementTable Maven / Gradle / Ivy
package org.hl7.fhir.r5.renderers.utils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.CanonicalType;
import org.hl7.fhir.r5.model.CodeableConcept;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.DataType;
import org.hl7.fhir.r5.model.ElementDefinition;
import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
import org.hl7.fhir.r5.model.Enumerations.BindingStrength;
import org.hl7.fhir.r5.model.Extension;
import org.hl7.fhir.r5.model.Quantity;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.r5.renderers.DataRenderer;
import org.hl7.fhir.r5.renderers.Renderer.RenderingStatus;
import org.hl7.fhir.r5.renderers.utils.ElementTable.HintDrivenGroupingEngine;
import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome;
import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.json.model.JsonArray;
import org.hl7.fhir.utilities.json.model.JsonObject;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel;
import org.hl7.fhir.utilities.xhtml.NodeType;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
@MarkedToMoveToAdjunctPackage
public class ElementTable {
public static class ElementTableGrouping {
private long key;
private String name;
private int priority;
public ElementTableGrouping(long key, String name, int priority) {
this.key = key;
this.name = name;
this.priority = priority;
}
public String getName() {
return name;
}
public int getPriority() {
return priority;
}
public long getKey() {
return key;
}
}
public enum ElementTableGroupingState {
UNKNOWN, DEFINES_GROUP, IN_GROUP
}
public static abstract class ElementTableGroupingEngine {
public abstract ElementTableGroupingState groupState(ElementDefinition ed);
public abstract ElementTableGrouping getGroup(ElementDefinition ed);
protected boolean nameMatches(String name, List strings) {
for (String s : strings) {
if (nameMatches(name, s)) {
return true;
}
}
return false;
}
public boolean nameMatches(String name, String test) {
if (test.equals(name)) {
return true;
}
if (test.endsWith("[x]") && name.startsWith(test.substring(0, test.length()-3))) {
return true;
}
return false;
}
}
public static class JsonDrivenGroupingEngine extends ElementTableGroupingEngine {
private JsonArray groups;
public JsonDrivenGroupingEngine(JsonArray groups) {
super();
this.groups = groups;
}
public ElementTableGroupingState groupState(ElementDefinition ed) {
String name = ed.getName();
for (JsonObject o : groups.asJsonObjects() ) {
if (nameMatches(name, o.getStrings("elements"))) {
return ElementTableGroupingState.IN_GROUP;
}
}
for (JsonObject o : groups.asJsonObjects() ) {
if (o.asBoolean("all")) {
return ElementTableGroupingState.IN_GROUP;
}
}
return ElementTableGroupingState.UNKNOWN;
}
public ElementTableGrouping getGroup(ElementDefinition ed) {
String name = ed.getName();
int c = 0;
for (JsonObject o : groups.asJsonObjects() ) {
c++;
if (nameMatches(name, o.getStrings("elements"))) {
return new ElementTableGrouping(c, o.asString("name"), groups.size() - c);
}
}
c = 0;
for (JsonObject o : groups.asJsonObjects() ) {
c++;
if (o.asBoolean("all")) {
return new ElementTableGrouping(c, o.asString("name"), groups.size() - c);
}
}
return null;
}
}
public static class HintDrivenGroupingEngine extends ElementTableGroupingEngine {
private List list;
public HintDrivenGroupingEngine(List list) {
super();
this.list = list;
}
public ElementTableGroupingState groupState(ElementDefinition ed) {
if (ed.hasExtension(ToolingExtensions.EXT_PROFILE_VIEW_HINT)) {
List exl = ed.getExtensionsByUrl(ToolingExtensions.EXT_PROFILE_VIEW_HINT);
for (Extension ex : exl) {
if ("element-view-group".equals(ex.getExtensionString("name")) ) {
return ElementTableGroupingState.DEFINES_GROUP;
}
}
}
return ElementTableGroupingState.UNKNOWN;
}
public ElementTableGrouping getGroup(ElementDefinition ed) {
if (ed.hasExtension(ToolingExtensions.EXT_PROFILE_VIEW_HINT)) {
String n = null;
int order = 0;
List exl = ed.getExtensionsByUrl(ToolingExtensions.EXT_PROFILE_VIEW_HINT);
for (Extension ex : exl) {
if ("element-view-group".equals(ex.getExtensionString("name")) ) {
n = ex.getExtensionString("value");
}
if ("element-view-order".equals(ex.getExtensionString("name")) ) {
order = ToolingExtensions.readIntegerExtension(ex, "value", 0);
}
}
if (n != null) {
return new ElementTableGrouping(ed.getName().hashCode(), n, order);
}
}
return null;
}
}
public static enum TableElementDefinitionType {
DEFINITION, COMMENT, REQUIREMENTS;
}
public static class TableElementDefinition {
private TableElementDefinitionType type;
private String markdown;
public TableElementDefinition(TableElementDefinitionType type, String markdown) {
super();
this.type = type;
this.markdown = markdown;
}
public TableElementDefinitionType getType() {
return type;
}
public String getMarkdown() {
return markdown;
}
}
public enum TableElementConstraintType {
CHOICE, PROFILE, TARGET, BINDING, RANGE, FIXED, PATTERN, MAXLENGTH, CARDINALITY;
}
public static class TableElementConstraint {
private TableElementConstraintType type;
private DataType value;
private DataType value2;
private String path;
private BindingStrength strength;
private String valueSet;
private List types;
private List list;
public static TableElementConstraint makeValue(TableElementConstraintType type, String path, DataType value) {
TableElementConstraint self = new TableElementConstraint();
self.type = type;
self.path = path;
self.value = value;
return self;
}
public static TableElementConstraint makeValueVS(TableElementConstraintType type, String path, DataType value, BindingStrength strength, String valueSet) {
TableElementConstraint self = new TableElementConstraint();
self.type = type;
self.path = path;
self.value = value;
self.strength = strength;
self.valueSet = valueSet;
return self;
}
public static TableElementConstraint makeRange(TableElementConstraintType type, String path, DataType value, DataType value2) {
TableElementConstraint self = new TableElementConstraint();
self.type = type;
self.path = path;
self.value = value;
self.value2 = value2;
return self;
}
public static TableElementConstraint makeBinding(TableElementConstraintType type, String path, BindingStrength strength, String valueSet) {
TableElementConstraint self = new TableElementConstraint();
self.type = type;
self.path = path;
self.strength = strength;
self.valueSet = valueSet;
return self;
}
public static TableElementConstraint makeTypes(TableElementConstraintType type, String path, List types) {
TableElementConstraint self = new TableElementConstraint();
self.type = type;
self.path = path;
self.types = types;
return self;
}
public static TableElementConstraint makeList(TableElementConstraintType type, String path, List list) {
TableElementConstraint self = new TableElementConstraint();
self.type = type;
self.path = path;
self.list = list;
return self;
}
// private BindingStrength strength;
// private ValueSet vs;
// private List profiles;
// private List targetProfiles;
// private DataType fixed;
// private DataType pattern;
// private DataType minValue;
// private DataType maxValue;
}
public class ConstraintsSorter implements Comparator {
@Override
public int compare(TableElementConstraint o1, TableElementConstraint o2) {
int r = StringUtils.compare(o1.path, o2.path);
return r == 0 ? o1.type.compareTo(o2.type) : r;
}
}
public static class TableElementInvariant {
private String level;
private String human;
private String fhirPath;
private String other;
private String otherFormat;
}
public static class TableElement {
// column 1
private String path;
private String name;
private String min;
private String max;
private String typeName;
private String typeIcon;
private String typeLink;
private String typeHint;
// column 2
private List definitions = new ArrayList<>();
// column 3
private List constraints = new ArrayList<>();
private List invariants = new ArrayList<>();
private List childElements = new ArrayList();
public TableElement(String path, String name, String min, String max) {
super();
this.path = path;
this.name = name;
this.min = min;
this.max = max;
}
public String getPath() {
return path;
}
public String getName() {
return name;
}
public String getMin() {
return min;
}
public String getMax() {
return max;
}
public String getTypeName() {
return typeName;
}
public String getTypeIcon() {
return typeIcon;
}
public String getTypeLink() {
return typeLink;
}
public String getTypeHint() {
return typeHint;
}
public List getDefinitions() {
return definitions;
}
public List getConstraints() {
return constraints;
}
public List getInvariants() {
return invariants;
}
public List getChildElements() {
return childElements;
}
public TableElement setPath(String path) {
this.path = path;
return this;
}
public TableElement setName(String name) {
this.name = name;
return this;
}
public TableElement setMin(String min) {
this.min = min;
return this;
}
public TableElement setMax(String max) {
this.max = max;
return this;
}
public void setType(String name, String link, String hint, String icon) {
this.typeName = name;
this.typeIcon = icon;
this.typeLink = link;
this.typeHint = hint;
}
}
public static class TableGroup {
private String name;
private String documentation;
private boolean buildIfEmpty;
private String emptyNote;
private List elements = new ArrayList();
private int priorty;
private int counter;
private ElementTableGrouping definition;
public TableGroup(int counter, ElementTableGrouping definition) {
super();
this.counter = counter;
this.definition = definition;
name = definition.getName();
priorty = definition.getPriority();
buildIfEmpty = false;
}
public String getName() {
return name;
}
public String getDocumentation() {
return documentation;
}
public boolean isBuildIfEmpty() {
return buildIfEmpty;
}
public String getEmptyNote() {
return emptyNote;
}
public List getElements() {
return elements;
}
public int getPriorty() {
return priorty;
}
public int getCounter() {
return counter;
}
public ElementTableGrouping getDefinition() {
return definition;
}
}
public static class TableGroupSorter implements Comparator {
@Override
public int compare(TableGroup o1, TableGroup o2) {
if (o1.priorty == o2.priorty) {
return Integer.compare(o1.counter, o2.counter);
} else {
return Integer.compare(o2.priorty, o1.priorty); // priorty sorts backwards
}
}
}
private RenderingContext context;
private List groups;
private DataRenderer dr;
private boolean replaceCardinality;
public ElementTable(RenderingContext context, List groups, DataRenderer dr, boolean replaceCardinality) {
this.context = context;
this.groups = groups;
this.dr = dr;
this.replaceCardinality = replaceCardinality;
}
public void build(HierarchicalTableGenerator gen, TableModel table) throws FHIRFormatError, DefinitionException, IOException {
Collections.sort(groups, new TableGroupSorter());
table.setBorder(true);
table.setShowHeadings(false);
for (TableGroup grp : groups) {
if (grp.getElements().size() > 0 || grp.buildIfEmpty) {
renderGroup(gen, table, grp);
}
}
}
private void renderGroup(HierarchicalTableGenerator gen, TableModel table, TableGroup grp) throws FHIRFormatError, DefinitionException, IOException {
Row row = gen.new Row();
table.getRows().add(row);
Cell cell = gen.new Cell(null, null, grp.getName(), null, null);
row.getCells().add(cell);
cell.span(3);
row.setColor("#dfdfdf");
cell.addStyle("vertical-align: middle");
cell.addStyle("font-weight: bold");
cell.addStyle("font-size: 14px");
cell.addStyle("padding-top: 10px");
cell.addStyle("padding-bottom: 10px");
boolean first = true;
for (TableElement e : grp.elements) {
renderElement(gen, row.getSubRows(), e, first);
first = false;
}
}
private void renderElement(HierarchicalTableGenerator gen, List rows, TableElement e, boolean first) throws FHIRFormatError, DefinitionException, IOException {
Row row = gen.new Row();
rows.add(row);
if (!first) {
row.setTopLine("silver");
}
renderElementIdentity(gen, row, e);
renderElementDefinition(gen, row, e);
renderElementConstraints(gen, row, e);
// if (e.invariants.size() > 0) {
// tr.style("border-bottom: none");
// tr = table.tr();
// tr.style("border-top: none");
// tr.style("border-left: black 1px solid");
// tr.style("border-right: black 1px solid");
// renderElementInvariants(tr.td().colspan(3), e);
// }
// tr.style("border-bottom: silver 1px solid");
for (TableElement child : e.getChildElements()) {
renderElement(gen, row.getSubRows(), child, false);
}
}
public void renderElementIdentity(HierarchicalTableGenerator gen, Row row, TableElement e) {
Cell cell = gen.new Cell();
cell.addCellStyle("min-width: 220px");
row.getCells().add(cell);
cell.setInnerTable(true);
cell.addText(e.getName()).addStyle("font-weight: bold");
cell.addPiece(gen.new Piece("br"));
if (!replaceCardinality) {
cell.addText("Cardinality: "+e.min+".."+e.max);
} else if ("1".equals(e.min) && "1".equals(e.max)) {
cell.addText("Required");
} else if ("0".equals(e.min) && "*".equals(e.max)) {
cell.addText("Optional, Repeating");
} else if ("0".equals(e.min) && "1".equals(e.max)) {
cell.addText("Optional");
} else if ("1".equals(e.min) && "*".equals(e.max)) {
cell.addText("Repeating");
} else {
cell.addText("Cardinality: "+e.min+".."+e.max);
}
cell.addPiece(gen.new Piece("br"));
cell.addImg(e.getTypeIcon(), e.getTypeHint(), e.getTypeLink());
cell.addPiece(gen.new Piece(e.getTypeLink(), " "+e.getTypeName(), e.getTypeHint()));
}
public void renderElementConstraints(HierarchicalTableGenerator gen, Row row, TableElement e) throws FHIRFormatError, DefinitionException, IOException {
Cell cell = gen.new Cell();
cell.addCellStyle("min-width: 300px");
row.getCells().add(cell);
XhtmlNode div = new XhtmlNode(NodeType.Element, "div");
Collections.sort(e.getConstraints(), new ConstraintsSorter());
boolean first = true;
for (TableElementConstraint c : e.getConstraints()) {
if (first) first= false; else div.br();
switch (c.type) {
case BINDING:
renderBindingConstraint(div, c);
break;
case CHOICE:
renderChoiceConstraint(div, c);
break;
case FIXED:
renderValueConstraint(div, c);
break;
case MAXLENGTH:
renderValueConstraint(div, c);
break;
case PATTERN:
renderValueConstraint(div, c);
break;
case PROFILE:
renderListConstraint(div, c);
break;
case RANGE:
renderRangeConstraint(div, c);
break;
case TARGET:
renderListConstraint(div, c);
break;
case CARDINALITY:
renderCardinalityConstraint(div, c);
break;
default:
break;
}
}
cell.addXhtml(div);
}
private void renderBindingConstraint(XhtmlNode x, TableElementConstraint c) {
String name = c.path == null ? "value" : c.path;
x.code().tx(name);
renderBinding(x, c, " is bound to ");
}
private void renderBinding(XhtmlNode x, TableElementConstraint c, String phrase) {
ValueSet vs = context.getContext().findTxResource(ValueSet.class, c.valueSet);
if (vs == null) {
x.tx(phrase+"an unknown valueset ");
x.code().tx(c.valueSet);
} else {
x.tx(phrase);
x.ah(vs.getWebPath()).tx(vs.present());
try {
ValueSetExpansionOutcome exp = context.getContext().expandVS(vs, true, false);
if (!exp.isOk()) {
x.span().attribute("title", exp.getError()).tx(" (??)");
} else if (exp.getValueset().getExpansion().getContains().size() == 1000) {
x.tx(" (>1000 codes)");
} else if (exp.getValueset().getExpansion().getContains().size() > 6) {
x.tx(" ("+exp.getValueset().getExpansion().getContains().size()+" codes)");
} else {
x.tx(". Codes:");
XhtmlNode ul = x.ul();
for (ValueSetExpansionContainsComponent cc : exp.getValueset().getExpansion().getContains()) {
String url = cc.hasSystem() && cc.hasCode() ? dr.getLinkForCode(cc.getSystem(), cc.getVersion(), cc.getCode()) : null;
var li = ul.li();
li.ahOrNot(url).tx(dr.displayCodeSource(cc.getSystem(), cc.getVersion())+": "+cc.getCode());
if (cc.hasDisplay()) {
li.tx(" \""+cc.getDisplay()+"\"");
}
}
}
} catch (Exception e) {
x.span().attribute("title", e.getMessage()).tx(" (??)");
}
}
}
private void renderChoiceConstraint(XhtmlNode x, TableElementConstraint c) {
String name = c.path == null ? "value" : c.path;
x.code().tx(name);
x.tx(" is a choice of:");
var ul = x.ul();
for (TypeRefComponent tr : c.types) {
if (tr.hasProfile()) {
for (CanonicalType ct : tr.getProfile()) {
StructureDefinition sd = context.getContext().fetchTypeDefinition(ct.primitiveValue());
if (sd == null || !sd.hasWebPath()) {
ul.li().ah(ct.primitiveValue()).tx(ct.primitiveValue());
} else {
ul.li().ah(sd.getWebPath()).tx(sd.present());
}
}
} else if (tr.hasTarget()) {
StructureDefinition sd = context.getContext().fetchTypeDefinition(tr.getWorkingCode());
var li = ul.li();
li.ah(sd.getWebPath()).tx(sd.present());
li.tx(" pointing to ");
renderTypeList(x, tr.getTargetProfile());
} else {
StructureDefinition sd = context.getContext().fetchTypeDefinition(tr.getWorkingCode());
if (sd == null || !sd.hasWebPath()) {
ul.li().code().tx(tr.getWorkingCode());
} else {
ul.li().ah(sd.getWebPath()).tx(sd.present());
}
}
}
}
private void renderValueConstraint(XhtmlNode x, TableElementConstraint c) throws FHIRFormatError, DefinitionException, IOException {
String name = c.path == null ? "value" : c.path;
x.code().tx(name);
switch (c.type) {
case FIXED:
x.tx(" is fixed to ");
break;
case MAXLENGTH:
x.tx(" is limited in length to ");
break;
case PATTERN:
if (c.value.isPrimitive()) {
x.tx(" is fixed to ");
} else {
x.tx(" must match ");
}
break;
default:
break;
}
renderValue(x, c.value);
if (c.strength != null && c.valueSet != null) {
renderBinding(x, c, " from ");
}
}
public void renderValue(XhtmlNode x, DataType v) throws IOException {
if (v.isPrimitive()) {
String s = v.primitiveValue();
if (Utilities.isAbsoluteUrl(s)) {
Resource res = context.getContext().fetchResource(Resource.class, s);
if (res != null && res.hasWebPath()) {
x.ah(res.getWebPath()).tx(res instanceof CanonicalResource ? ((CanonicalResource) res).present() : s);
} else if (Utilities.isAbsoluteUrlLinkable(s)) {
x.ah(s).code().tx(s);
} else {
x.code().tx(s);
}
} else {
x.code().tx(s);
}
} else if (v instanceof Quantity) {
genQuantity(x, (Quantity) v);
} else if (v instanceof Coding) {
genCoding(x, (Coding) v);
} else if (v instanceof CodeableConcept) {
genCodeableConcept(x, (CodeableConcept) v);
} else {
dr.renderBase(new RenderingStatus(), x, v);
}
}
private void genCodeableConcept(XhtmlNode div, CodeableConcept cc) {
boolean first = true;
for (Coding c : cc.getCoding()) {
if (first) first = false; else div.tx(",");
genCoding(div, c);
}
if (cc.hasText()) {
div.code().tx(" \""+cc.getText()+"\"");
}
}
public void genQuantity(XhtmlNode div, Quantity q) {
String url = q.hasSystem() && q.hasUnit() ? dr.getLinkForCode(q.getSystem(), null, q.getCode()) : null;
var code = div.code();
if (q.hasComparator()) {
code.tx(q.getComparator().toCode());
}
code.tx(q.getValueElement().asStringValue());
code.ahOrNot(url).tx(q.getUnit());
}
public void genCoding(XhtmlNode div, Coding c) {
String url = c.hasSystem() && c.hasCode() ? dr.getLinkForCode(c.getSystem(), c.getVersion(), c.getCode()) : null;
var code = div.code();
code.ahOrNot(url).tx(dr.displayCodeSource(c.getSystem(), c.getVersion())+": "+c.getCode());
if (c.hasDisplay()) {
code.tx(" \""+c.getDisplay()+"\"");
} else {
String s = dr.lookupCode(c.getSystem(), c.getVersion(), c.getCode());
if (s != null) {
div.span().style("opacity: 0.5").tx("(\""+s+"\")");
}
}
}
private void renderRangeConstraint(XhtmlNode x, TableElementConstraint c) throws IOException {
String name = c.path == null ? "value" : c.path;
if (c.value != null && c.value2 != null) {
x.tx(name + " between ");
renderValue(x, c.value);
x.tx(" and " );
renderValue(x, c.value2);
} else if (c.value != null) {
x.tx(name + " more than ");
renderValue(x, c.value);
} else {
x.tx(name + " less than ");
renderValue(x, c.value2);
}
}
private void renderCardinalityConstraint(XhtmlNode x, TableElementConstraint c) throws IOException {
String name = c.path == null ? "value" : c.path;
x.code().tx(name);
String min = c.value.primitiveValue();
String max = c.value2.primitiveValue();
if (!replaceCardinality) {
x.tx("has cardinality: "+min+".."+max);
} else if ("1".equals(min) && "1".equals(max)) {
x.tx("is required");
} else if ("0".equals(min) && "*".equals(max)) {
x.tx("is Optional and repeats");
} else if ("0".equals(min) && "1".equals(max)) {
x.tx("is Optional");
} else if ("1".equals(min) && "*".equals(max)) {
x.tx("repeats");
} else {
x.tx("has cardinality: "+min+".."+max);
}
}
private void renderListConstraint(XhtmlNode x, TableElementConstraint c) {
String name = c.path == null ? "value" : c.path;
x.code().tx(name);
switch (c.type) {
case PROFILE:
x.tx(" must be ");
break;
case TARGET:
x.tx(" must point to ");
break;
default:
break;
}
renderTypeList(x, c.list);
}
private void renderTypeList(XhtmlNode x, List list) {
if (list.size() == 1) {
x.tx("a ");
} else {
x.tx("one of ");
}
boolean first = true;
for (int i = 0; i < list.size(); i++) {
if (first) {
first = false;
} else if (i == list.size() - 1) {
x.tx(" or " );
} else {
x.tx(", ");
}
String s = list.get(i).primitiveValue();
Resource res = context.getContext().fetchResource(Resource.class, s);
if (res != null && res.hasWebPath()) {
x.ah(res.getWebPath()).tx(res instanceof CanonicalResource ? ((CanonicalResource) res).present() : s);
} else {
x.ah(s).tx(s);
}
}
}
public void renderElementDefinition(HierarchicalTableGenerator gen, Row row, TableElement e) {
Cell cell = gen.new Cell(); row.getCells().add(cell);
for (TableElementDefinition d : e.definitions) {
if (d.getType() == TableElementDefinitionType.DEFINITION) {
cell.addMarkdown(d.getMarkdown());
} else if (d.getType() == TableElementDefinitionType.COMMENT) {
cell.addMarkdown("Comment: "+d.getMarkdown(), "font-style: italic");
}
}
}
private void renderElementInvariants(XhtmlNode td, TableElement e) {
XhtmlNode ul = td.ul();
for (TableElementInvariant t : e.invariants) {
var li = ul.li();
li.tx(t.level+": "+t.human);
li.tx(" ");
li.code().tx(t.other != null ? t.other : t.fhirPath);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy